def GetCredentials(flags, scope_list): """Retrieve saved credentials or create and save credentials using flow. Args: flags: argparse parsed flags object. scope_list: List of strings reflecting desired API access (scope) e.g.: ['https://www.googleapis.com/auth/directory.user']. Returns: An oauth2client Credentials() object. """ client_file_storage = Storage( FILE_MANAGER.BuildFullPathToFileName(_CURRENT_ACCESS_FILE_NAME)) credentials = client_file_storage.get() if credentials is None or credentials.invalid: client_secrets_path = FILE_MANAGER.BuildFullPathToFileName( _CLIENT_SECRETS_FILE_NAME, work_dir=False) missing_secrets_msg = _MISSING_CLIENT_SECRETS_MSG % client_secrets_path flow_manager = flow_from_clientsecrets(client_secrets_path, scope=scope_list, message=missing_secrets_msg) credentials = run_flow(flow_manager, client_file_storage, flags) if not credentials: log_utils.LogError('Unable to retrieve valid credentials.') sys.exit(-1) return credentials
def WriteDefaults(self, apps_domain, customer_id, overwrite_ok): """Write apps_domain and customer_id to a file for later use. Writes data as strings in a dictionary. Args: apps_domain: String reflecting an apps domain: altostrat.com customer_id: Unique id for an owner of apps domain(s). Can map to multiple domains. overwrite_ok: Boolean that is True if the user has explicitly approved that an existing file may be overwritten. Returns: String with the fully path'ed file name. Raises: AdminAPIToolError: if blank supplied for domain or customer_id. """ if not apps_domain or not customer_id: raise admin_api_tool_errors.AdminAPIToolError( 'Unexpectedly empty defaults to write!') try: filename_path = self.WriteJsonFile( self.DEFAULT_DOMAIN_FILE_NAME, {'apps_domain': apps_domain, 'customer_id': customer_id}, work_dir=False, overwrite_ok=overwrite_ok) except admin_api_tool_errors.AdminAPIToolError as e: log_utils.LogError('Unable to write defaults file (%s).' % e) sys.exit(1) return filename_path
def GetAuthorizedHttp(flags): """Helper to create an http interface object and authorize it. Made simple by oauth2client library. Because http object are NOT thread-safe, create a new one every time (assumes being created by multiple threads). Args: flags: argparse parsed flags object. Returns: Authorized httplib2 http interface object. """ credentials = GetCredentials(flags, _SCOPES) try: cse_tool_version = _TOOL_USER_AGENT % FILE_MANAGER.ReadAppVersion() log_utils.LogDebug('user-agent: %s' % cse_tool_version) http = httplib2.Http(timeout=_EXTENDED_SOCKET_TIMEOUT_S) set_user_agent(http, cse_tool_version) http = credentials.authorize(http) except AccessTokenRefreshError: log_utils.LogError('The credentials have been revoked or expired, ' 'please re-run the application to re-authorize') sys.exit(1) return http
def ReadTextFileToSet(self, file_name): """Reads text file lines into a set; each line is an entry. Args: file_name: file name - expected located in the working directory. Returns: Set object reflecting the file contents. """ if not self.FileExists(file_name): log_utils.LogError('Unable to locate file: %s' % self.BuildFullPathToFileName(file_name)) sys.exit(1) line_set = set() for line in self.ReadTextFile(file_name).splitlines(): line_set.add(line) return line_set
def _GetDomainUsersData(http, flags): """Helper to get the user data for a domain that will be used in the queries. Args: http: Authorized http interface. flags: Argparse flags object with apps_domain, resume and first_n. Returns: Tuple of: user_list: list of user tuples. user_count: count of users so others can avoid len(user_list). """ try: user_list, user_count = _GetUserList(http, flags) except admin_api_tool_errors.AdminAPIToolUserError as e: log_utils.LogError('Unable to retrieve required users data.', e) sys.exit(1) return user_list, user_count
def GetTokenStats(exit_on_fail=True): """Reads the snapshot of the token stats from the Json file. Args: exit_on_fail: Alternately return the message instead of failing if token file not found. Used for ui reporting. Returns: Token stats in an object (a dictionary). If cannot find the file return a message to show. """ if not FILE_MANAGER.FileExists(_TOKENS_ISSUED_FILE_NAME): message = 'No token data. You must run gather_domain_token_stats first.' log_utils.LogError(message) if exit_on_fail: sys.exit(1) else: return message return FILE_MANAGER.ReadJsonFile(_TOKENS_ISSUED_FILE_NAME)
def _GetUserList(http, flags): """Helper to retrieve the user list from local file or request. The list may be 10's of thousands of users so we prefer to keep a cached copy local for user_id lookups. Args: http: An authorized http interface object. flags: Argparse flags object with apps_domain, resume and first_n. Returns: A list of user tuples. For example: [["*****@*****.**", "000000000298938768732", "George Lasta"], ["*****@*****.**", "000000000406809560189", "usertest0 userlast"], ["*****@*****.**", "000000000766612723480", "usertest100 userlast"]] """ # Need a list of users in the domain. if FILE_MANAGER.FileExists(FILE_MANAGER.USERS_FILE_NAME): log_utils.LogInfo('Using existing users list last modified on %s.' % FILE_MANAGER.FileTime(FILE_MANAGER.USERS_FILE_NAME)) users_list = FILE_MANAGER.ReadJsonFile(FILE_MANAGER.USERS_FILE_NAME) # Verify that the domain has not changed if users_list: domain = validators.GetEmailParts(users_list[0][0])[1] if domain != flags.apps_domain: log_utils.LogError( 'You have requested to use domain %s, but your existing users ' 'file \n(%s) was generated using\n%s. Please remove the file or ' 'specify %s as your apps_domain.' % ( flags.apps_domain, FILE_MANAGER.BuildFullPathToFileName( FILE_MANAGER.USERS_FILE_NAME), domain, domain)) sys.exit(1) else: log_utils.LogInfo('Retrieving list of users...') api_wrapper = users_api.UsersApiWrapper(http) users_list = api_wrapper.GetDomainUsers(flags.apps_domain) FILE_MANAGER.WriteJsonFile(FILE_MANAGER.USERS_FILE_NAME, users_list) user_count = len(users_list) log_utils.LogInfo('Found %d users to check.' % user_count) return users_list, user_count
def WriteTokensIssuedJson(token_stats, overwrite_ok=False): """Writes the snapshot of the token stats to Json file in progress. Args: token_stats: An object with the collected token stats. overwrite_ok: If True don't check if file exists - else fail if file exists. Returns: String reflecting the full path of the file created/written. """ filename_path = FILE_MANAGER.BuildFullPathToFileName( _TOKENS_ISSUED_FILE_NAME) if FILE_MANAGER.FileExists(_TOKENS_ISSUED_FILE_NAME) and not overwrite_ok: log_utils.LogError('Output file (%s) already exists. Use --force to ' 'overwrite or --resume to continue an interrupted ' 'run.' % filename_path) sys.exit(1) filename_path = FILE_MANAGER.WriteJsonFile(_TOKENS_ISSUED_FILE_NAME, token_stats, overwrite_ok=overwrite_ok) return filename_path
def ExitIfCannotOverwriteFile(self, file_name, work_dir=True, overwrite_ok=False): """Helper to consistently handle attempts to overwrite existing files. Args: file_name: String name of a file (e.g. users.json). work_dir: Boolean, if True indicates to locate the file under a 'working' folder else locates the file in the base application directory. overwrite_ok: Boolean that is True if the user has explicitly approved that an existing file may be overwritten. """ if self.FileExists(file_name, work_dir=work_dir): not_writable_msg = None filename_path = self.BuildFullPathToFileName(file_name, work_dir=work_dir) exists_msg = 'Output file (%s) already exists.' % filename_path if not os.access(filename_path, os.W_OK): not_writable_msg = '%s %s' % ( exists_msg, 'The file permissions do not allow writing.') elif not overwrite_ok: not_writable_msg = '%s %s' % ( exists_msg, 'Use --force to overwrite.') if not_writable_msg: log_utils.LogError(not_writable_msg) sys.exit(1)
def StartUserIterator(http, prefix, flags): """Domain user iterator for resumably looping through all domain users. Handles the acquisition of the users list and checking of resume which makes the code to collect and revoke domain users much easier to read. Args: http: authorized http interface. prefix: custom prefix to identify progress file e.g. 'collect' or 'revoke'. flags: Argparse flags object with apps_domain, resume and first_n. Yields: A 3-Tuple of user data: -user email: String e.g. '*****@*****.**' -user id: String of ints e.g. '112351558298938768732' -checkpoint: True if batch full or on the last user. """ user_list, user_count = _GetDomainUsersData(http, flags) if flags.resume: # Resume: check that current users.json file still matches where we # left off and adjust the users list to skip previously checked. try: users_checked = CheckResumable(user_list, user_count, prefix, flags) except admin_api_tool_errors.AdminAPIToolResumeError as e: log_utils.LogError( 'Cannot --resume %s. You must retry without --resume.' % prefix, e) sys.exit(1) prev_user = user_list[users_checked - 1][0] print 'Resuming at user #%d/%d (%s)...' % (users_checked, user_count, user_list[users_checked][0]) else: users_checked = 0 prev_user = None # Allow users to test revoke with shorter lists. if flags.first_n: user_count = flags.first_n for user_email, user_id, _ in user_list[users_checked:user_count]: users_checked += 1 checkpoint = (users_checked % _USER_PROGRESS_CHECKPOINT_BATCH == 0 or users_checked == user_count) # Show some screen output during a longish, tedious process. # Each iteration seems to take ~0.6s sys.stdout.write('%80s\r' % '') # Clear the previous entry. sys.stdout.write('%s\r' % user_email) sys.stdout.flush() yield user_email, user_id, checkpoint # In the interest of possibly resuming very long runs, save progress # cookie. Track last 2 users checked because we may alternate cycling # through the list in asc or desc order to to create some entropy and # this leaves a hint of the direction that was used that run. _WriteLastUserProgress(prefix, prev_user, user_email, users_checked) prev_user = user_email if checkpoint: sys.stdout.write('%80s\r' % '') # Clear the previous entry. sys.stdout.write('Checked %d of %d users.\n' % (users_checked, user_count)) # Cleanup progress file to inhibit resuming completed tasks. _RemoveLastUserProgress(prefix)