From 92a57ca52e483c26ea7671b646d6d951a47b697c Mon Sep 17 00:00:00 2001 From: Alex Evans Date: Fri, 6 Nov 2020 15:35:12 -0700 Subject: [PATCH 1/2] Currently, the rest api bearer token is only valid for 2h. If a HubRestApi object is used by a client script in the examples directory to create a custom report for a large set of components, the report could run longer than 2h using the same token. Anything requested after 2h will get a 401 response from Black Duck Hub. This enhancement will solve that potential problem since there is no way around it on the Black Duck Hub side. On each execute_get call, check to see if the current HubRestApi object has existed for less than 1.5h, if older than 1.5h, reinitialize it with a new bearer token. --- blackduck/HubRestApi.py | 42 ++++++++++++++++++++++++++++++++++++++--- 1 file changed, 39 insertions(+), 3 deletions(-) diff --git a/blackduck/HubRestApi.py b/blackduck/HubRestApi.py index 4243c857..2e413789 100755 --- a/blackduck/HubRestApi.py +++ b/blackduck/HubRestApi.py @@ -47,6 +47,8 @@ ''' import logging +import os +import time import requests import json from operator import itemgetter @@ -86,11 +88,14 @@ class HubInstance(object): # TODO: What to do about the config file for thread-safety, concurrency configfile = ".restconfig.json" - + global root_dir + # For refresh to find the .restconfig.json file upon reinitialization, + # client script must be executed in the same directory + root_dir = os.getcwd() def __init__(self, *args, **kwargs): # Config needs to be an instance variable for thread-safety, concurrent use of HubInstance() self.config = {} - + self.read_config() try: self.config['baseurl'] = args[0] api_token = kwargs.get('api_token', False) @@ -123,6 +128,9 @@ def __init__(self, *args, **kwargs): def read_config(self): try: + #always return to the examples directory to read the config + if not os.getcwd().endswith("examples"): + os.chdir(root_dir) with open('.restconfig.json','r') as f: self.config = json.load(f) except: @@ -151,6 +159,12 @@ def get_auth_token(self): except json.decoder.JSONDecodeError as e: logger.exception("Authentication failure, could not obtain bearer token") raise Exception("Failed to obtain bearer token, check for valid authentication token") + length = len(response.text) + new_token = response.text + token_tail = new_token[length - 50: length] + print ("New token requested, using restconfig api token: {}".format(token_tail)) + # set token expiration to 1.5h + self.access_token_expiration = time.time() + 5200 return (bearer_token, csrf_token, None) else: authendpoint="/j_spring_security_check" @@ -162,6 +176,9 @@ def get_auth_token(self): response = session.post(url, credentials, verify= not self.config['insecure']) cookie = response.headers['Set-Cookie'] token = cookie[cookie.index('=')+1:cookie.index(';')] + print ("New token requested with username and password in restconfig: {}".format(response.text)) + # set token expiration to 1.5h + self.access_token_expiration = time.time() + 5200 return (token, None, cookie) def _get_hub_rest_api_version_info(self): @@ -231,7 +248,26 @@ def get_limit_paramstring(self, limit): def get_apibase(self): return self.config['baseurl'] + "/api" - + + def reauthenticate(self, *args, **kwargs): + try: + self.__init__(self, *args, **kwargs) + except Exception as e: + print("There was an error when refreshing the token: {}".format(e)) + return None + else: + return self + + class Decorators(): + @staticmethod + def refresh_token(decorated): + def wrapper(hub_api_object, *args, **kwargs): + if time.time() > hub_api_object.access_token_expiration: + hub_api_object.reauthenticate(hub_api_object, *args, **kwargs) + return decorated(hub_api_object, *args, **kwargs) + + return wrapper + ### # # Role stuff From 03ead3e8698940f32644d619633500722f2886f1 Mon Sep 17 00:00:00 2001 From: Alex Evans Date: Fri, 20 Nov 2020 12:10:16 -0700 Subject: [PATCH 2/2] Remove prints of token refresh information. Make refresh_token a keyword argument for the constructor and leave it off by default. --- blackduck/HubRestApi.py | 41 ++++++++++++++++++++--------------------- 1 file changed, 20 insertions(+), 21 deletions(-) diff --git a/blackduck/HubRestApi.py b/blackduck/HubRestApi.py index 2e413789..3308c294 100755 --- a/blackduck/HubRestApi.py +++ b/blackduck/HubRestApi.py @@ -92,25 +92,28 @@ class HubInstance(object): # For refresh to find the .restconfig.json file upon reinitialization, # client script must be executed in the same directory root_dir = os.getcwd() + refresh_token = False def __init__(self, *args, **kwargs): # Config needs to be an instance variable for thread-safety, concurrent use of HubInstance() self.config = {} self.read_config() - try: - self.config['baseurl'] = args[0] - api_token = kwargs.get('api_token', False) - if api_token: - self.config['api_token'] = api_token - else: - self.config['username'] = args[1] - self.config['password'] = args[2] - self.config['insecure'] = kwargs.get('insecure', False) - self.config['debug'] = kwargs.get('debug', False) - - if kwargs.get('write_config_flag', True): - self.write_config() - except Exception: - self.read_config() + self.refresh_token = kwargs.get('refresh_token') + if not kwargs.get('refresh_token', False): + try: + self.config['baseurl'] = args[0] + api_token = kwargs.get('api_token', False) + if api_token: + self.config['api_token'] = api_token + else: + self.config['username'] = args[1] + self.config['password'] = args[2] + self.config['insecure'] = kwargs.get('insecure', False) + self.config['debug'] = kwargs.get('debug', False) + + if kwargs.get('write_config_flag', True): + self.write_config() + except Exception: + self.read_config() if self.config['insecure']: requests.packages.urllib3.disable_warnings() @@ -159,10 +162,6 @@ def get_auth_token(self): except json.decoder.JSONDecodeError as e: logger.exception("Authentication failure, could not obtain bearer token") raise Exception("Failed to obtain bearer token, check for valid authentication token") - length = len(response.text) - new_token = response.text - token_tail = new_token[length - 50: length] - print ("New token requested, using restconfig api token: {}".format(token_tail)) # set token expiration to 1.5h self.access_token_expiration = time.time() + 5200 return (bearer_token, csrf_token, None) @@ -176,7 +175,6 @@ def get_auth_token(self): response = session.post(url, credentials, verify= not self.config['insecure']) cookie = response.headers['Set-Cookie'] token = cookie[cookie.index('=')+1:cookie.index(';')] - print ("New token requested with username and password in restconfig: {}".format(response.text)) # set token expiration to 1.5h self.access_token_expiration = time.time() + 5200 return (token, None, cookie) @@ -251,6 +249,7 @@ def get_apibase(self): def reauthenticate(self, *args, **kwargs): try: + kwargs.update({"refresh_token":self.refresh_token}) self.__init__(self, *args, **kwargs) except Exception as e: print("There was an error when refreshing the token: {}".format(e)) @@ -262,7 +261,7 @@ class Decorators(): @staticmethod def refresh_token(decorated): def wrapper(hub_api_object, *args, **kwargs): - if time.time() > hub_api_object.access_token_expiration: + if time.time() > hub_api_object.access_token_expiration and hub_api_object.refresh_token: hub_api_object.reauthenticate(hub_api_object, *args, **kwargs) return decorated(hub_api_object, *args, **kwargs)