def __init__(self, Config=None): if isinstance(Config, Configuration): self._conf = Config else: self._conf = Configuration() self._notifications = Notifications(self.getConfig()) self.ProvisionLoggers()
def test_set(self): conf = Configuration() self.assertFalse(conf.get('Logging', 'trace')) conf.set('trace', True, 'Logging') self.assertTrue(conf.get('Logging', 'trace')) conf.set('trace', False, 'Logging') self.assertFalse(conf.get('Logging', 'trace'))
def _log_line_count(self): conf = Configuration() if os.path.isfile(conf.get('Logging', 'file')): fil = open(conf.get('Logging', 'file'), 'r') filContent = fil.read().splitlines() fil.close() del fil ctn = 0 for line in filContent: ctn += 1 del filContent del conf return ctn else: del conf return 0
def mock_data(self, configuration): """Data from this source is mocked utilizing the GREASE Filesystem Mock data for this source can be place in `<GREASE_DIR>/etc/*.mock.es.json`. This source will pick up all these files and load them into the returning object. The data in these files should reflect what you expect to return from ElasticSearch Args: configuration (dict): Configuration Data for source Note: Argument **configuration** is not honored here Returns: list[dict]: Mocked Data """ intermediate = list() matches = [] conf = Configuration() for root, dirnames, filenames in os.walk(conf.greaseDir + 'etc'): for filename in fnmatch.filter(filenames, '*.mock.es.json'): matches.append(os.path.join(root, filename)) for doc in matches: with open(doc) as current_file: content = current_file.read().replace('\r\n', '') try: intermediate.append(json.loads(content)) except ValueError: continue return intermediate
def test_defaults(self): conf = Configuration() self.assertEqual( conf.get('Connectivity', 'MongoDB').get('host'), 'localhost') self.assertEqual( conf.get('Connectivity', 'MongoDB').get('port'), 27017) self.assertEqual(conf.get('Logging', 'mode'), 'filesystem') self.assertEqual(conf.get('Logging', 'verbose'), False) self.assertEqual(conf.get('Logging', 'file'), conf.greaseDir + 'log' + conf.fs_sep + 'grease.log') self.assertEqual(conf.get('Configuration', 'dir'), conf.greaseDir + 'etc' + conf.fs_sep) self.assertEqual(conf.get('Sourcing', 'dir'), conf.greaseDir + 'etc' + conf.fs_sep)
def test_prototype_execution(self): ioc = GreaseContainer() cmd = DaemonProcess(ioc) # add search path fil = open(ioc.getConfig().greaseConfigFile, 'r') data = json.loads(fil.read()) fil.close() fil = open(ioc.getConfig().greaseConfigFile, 'w') data['Import']['searchPath'].append('tgt_grease.router.Commands.tests') fil.write(json.dumps(data, sort_keys=True, indent=4)) fil.close() Configuration.ReloadConfig() # Update Node to run it ioc.getCollection('JobServer')\ .update_one( {'_id': ObjectId(ioc.getConfig().NodeIdentity)}, { '$set': { 'prototypes': ['TestProtoType'] } } ) # Sleeps are because mongo in Travis is slow sometimes to persist data time.sleep(1.5) self.assertTrue(cmd.server()) self.assertTrue(cmd.drain_jobs(ioc.getCollection('JobQueue'))) # ensure jobs drain out time.sleep(1.5) self.assertEqual( ioc.getCollection('TestProtoType').find({ 'runs': { '$exists': True } }).count(), 10) # clean up fil = open(ioc.getConfig().greaseConfigFile, 'r') data = json.loads(fil.read()) fil.close() # remove collection ioc.getCollection('TestProtoType').drop() # pop search path trash = data['Import']['searchPath'].pop() # close out fil = open(ioc.getConfig().greaseConfigFile, 'w') fil.write(json.dumps(data, sort_keys=True, indent=4)) fil.close() ioc.getCollection('JobServer') \ .update_one( {'_id': ObjectId(ioc.getConfig().NodeIdentity)}, { '$set': { 'prototypes': [] } } )
def test_sql_parser_mock(self): source = sql_source() conf = Configuration() mock = {'id': 1, 'name_fs': 'sally', 'name_ls': 'sue'} fil = open(conf.greaseDir + 'etc' + conf.fs_sep + 'test.mock.sql.json', 'w') fil.write(json.dumps(mock)) fil.close() mockData = source.mock_data({}) self.assertEqual(len(mockData), 1) self.assertEqual(mock.get('id'), 1) self.assertEqual(mock.get('name_fs'), mockData[0].get('name_fs')) self.assertEqual(mock.get('name_ls'), mockData[0].get('name_ls')) os.remove(conf.greaseDir + 'etc' + conf.fs_sep + 'test.mock.sql.json')
def test_url_parser_mock(self): source = url_source() conf = Configuration() mock = { 'url': 'https://google.com', 'status_code': 200, 'headers': str({'test': 'ver', 'test1': 'val'}), 'body': 'welcome to google' } fil = open(conf.greaseDir + 'etc' + conf.fs_sep + 'test.mock.url.json', 'w') fil.write(json.dumps(mock)) fil.close() mockData = source.mock_data({}) self.assertEqual(len(mockData), 1) self.assertEqual(mock.get('url'), mockData[0].get('url')) self.assertEqual(mock.get('status_code'), mockData[0].get('status_code')) self.assertEqual(mock.get('headers'), mockData[0].get('headers')) self.assertEqual(mock.get('body'), mockData[0].get('body')) os.remove(conf.greaseDir + 'etc' + conf.fs_sep + 'test.mock.url.json')
def mock_data(self, configuration): """Data from this source is mocked utilizing the GREASE Filesystem Mock data for this source can be place in `<GREASE_DIR>/etc/*.mock.url.json`. This source will pick up all these files and load them into the returning object. They will need to follow this schema:: { 'url': String, # <-- URL that would have been loaded 'status_code': Int, # <-- HTTP Status code 'headers': String, # <-- HTTP headers as a string 'body': String # <-- HTTP response body } Args: configuration (dict): Configuration Data for source Note: Argument **configuration** is not honored here Returns: list[dict]: Mocked Data """ intermediate = list() matches = [] conf = Configuration() for root, dirnames, filenames in os.walk(conf.greaseDir + 'etc'): for filename in fnmatch.filter(filenames, '*.mock.url.json'): matches.append(os.path.join(root, filename)) for doc in matches: with open(doc) as current_file: content = current_file.read().replace('\r\n', '') try: intermediate.append(json.loads(content)) except ValueError: continue return intermediate
def __init__(self, Config=None): if Config and isinstance(Config, Configuration): self._conf = Config else: self._conf = Configuration()
def test_get_section(self): conf = Configuration() # test for section that should have stuff self.assertTrue(conf.get('Notifications')) # Section that is empty self.assertFalse(conf.get('Additional'))
def __init__(self, Config=None): if Config and isinstance(Config, Configuration): self._config = Config else: self._config = Configuration() self._client = self._generate_client()
def test_no_key(self): conf = Configuration() self.assertDictEqual({'MongoDB': { 'host': 'localhost', 'port': 27017 }}, conf.get('Connectivity'))
def test_default(self): conf = Configuration() self.assertTrue(conf.get('FakeSection', default=True)) self.assertTrue(conf.get('Connectivity', 'MongoDB'))
class Mongo(object): """MongoDB Connection Class Attributes: _client (pymongo.MongoClient): The actual PyMongo Connection _config (Configuration): Configuration Object """ _client = None _config = None def __init__(self, Config=None): if Config and isinstance(Config, Configuration): self._config = Config else: self._config = Configuration() self._client = self._generate_client() def Client(self): """get the connection client Returns: pymongo.MongoClient: Returns the mongoDB connection client """ return self._client def Close(self): """Close PyMongo Connection Returns: None: Void Method to close connection """ self._client.close() def _generate_client(self): """Creates a PyMongo Client Returns: pymongo.MongoClient: Mongo Connection """ mongoConf = self._config.get('Connectivity', 'MongoDB') # type: dict if mongoConf.get('username') and mongoConf.get('password'): return pymongo.MongoClient( "mongodb://{0}:{1}@{2}:{3}/{4}".format( mongoConf.get('username', ''), mongoConf.get('password', ''), mongoConf.get('host', 'localhost'), mongoConf.get('port', 27017), mongoConf.get('db', 'grease') ), w=1 ) else: return pymongo.MongoClient( host=mongoConf.get('host', 'localhost'), port=mongoConf.get('port', 27017), w=1 )
def test_filesystem(self): conf = Configuration() for elem in conf.FileSystem: self.assertTrue(os.path.isdir(conf.greaseDir + conf.fs_sep + elem))
class Logging(object): """Application Logging for GREASE This is the primary configuration source for GREASE logging. All log information will be passed here to enable centralized log aggregation Attributes: _conf (Configuration): This is an instance of the Config to enable configuring loggers _logger (logging.Logger): This is the actual logger for GREASE _formatter (logging.Formatter): This is the log formatter _notifications (Notifications): Notifications instance foreground (bool): If set will override config and print all log messages """ _conf = None _logger = None _formatter = None _notifications = None foreground = False def __init__(self, Config=None): if isinstance(Config, Configuration): self._conf = Config else: self._conf = Configuration() self._notifications = Notifications(self.getConfig()) self.ProvisionLoggers() def getConfig(self): """Getter for Configuration Returns: Configuration: The loaded configuration object """ return self._conf def getNotification(self): """Get Notification Class Returns: Notifications: The current Notifications instance """ return self._notifications def TriageMessage(self, message, additional=None, verbose=False, trace=False, notify=False, level=logging.DEBUG): """Central message handler Args: message (str): Message to Log additional (object): Additional information to log verbose (bool): To be printed if verbose is enabled trace (bool): To be printed if trace is enabled notify (bool): If true will pass through notification system level (int): Log Level Returns: bool: Log Success """ # first prevent verbose processing if verbose and not self._conf.get('Logging', 'verbose'): return True # prevent trace processing if trace and not self._conf.get('Logging', 'trace'): return True # create a pre-message if level is 0: preMsg = "TRACE" elif level is logging.DEBUG: preMsg = "DEBUG" elif level is logging.INFO: preMsg = "INFO" elif level is logging.WARNING: preMsg = "WARNING" elif level is logging.ERROR: preMsg = "ERROR" elif level is logging.CRITICAL: preMsg = "CRITICAL" else: preMsg = "UNSET" if verbose: preMsg = "VERBOSE::{0}".format(preMsg) if trace: preMsg = "TRACE::{0}".format(preMsg) if additional: message = "{0}::{1}::{2}::{3}".format(preMsg, self._conf.NodeIdentity, message, additional) else: message = "{0}::{1}::{2}".format(preMsg, self._conf.NodeIdentity, message) # Foreground mode print log messages if self._conf.get('Logging', 'foreground') or self.foreground: print("{0}::{1}".format(datetime.utcnow(), message)) # actually log the message if level is 0: self._logger.log(logging.DEBUG, message) else: self._logger.log(level, message) # notify if needed if notify: return bool(self._notifications.SendMessage(message, level)) return True def trace(self, message, additional=None, verbose=False, trace=True, notify=False): """Trace Messages Use this method for logging tracing (enhanced debug) statements Args: message (str): Message to log additional (object): Additional information to log. Note: object must be able to transform to string verbose (bool): Print only if verbose mode trace (bool): Print only if trace mode notify (bool): Run through the notification management system Returns: bool: Message is logged """ return bool(self.TriageMessage( message, additional=additional, verbose=verbose, trace=trace, notify=notify, level=0 )) def debug(self, message, additional=None, verbose=False, trace=False, notify=False): """Debug Messages Use this method for logging debug statements Args: message (str): Message to log additional (object): Additional information to log. Note: object must be able to transform to string verbose (bool): Print only if verbose mode trace (bool): Print only if trace mode notify (bool): Run through the notification management system Returns: bool: Message is logged """ return bool(self.TriageMessage( message, additional=additional, verbose=verbose, trace=trace, notify=notify, level=logging.DEBUG )) def info(self, message, additional=None, verbose=False, trace=False, notify=False): """Info Messages Use this method for logging info statements Args: message (str): Message to log additional (object): Additional information to log. Note: object must be able to transform to string verbose (bool): Print only if verbose mode trace (bool): Print only if trace mode notify (bool): Run through the notification management system Returns: bool: Message is logged """ return bool(self.TriageMessage( message, additional=additional, verbose=verbose, trace=trace, notify=notify, level=logging.INFO )) def warning(self, message, additional=None, verbose=False, trace=False, notify=False): """Warning Messages Use this method for logging warning statements Args: message (str): Message to log additional (object): Additional information to log. Note: object must be able to transform to string verbose (bool): Print only if verbose mode trace (bool): Print only if trace mode notify (bool): Run through the notification management system Returns: bool: Message is logged """ return bool(self.TriageMessage( message, additional=additional, verbose=verbose, trace=trace, notify=notify, level=logging.WARNING )) def error(self, message, additional=None, verbose=False, trace=False, notify=True): """Error Messages Use this method for logging error statements Args: message (str): Message to log additional (object): Additional information to log. Note: object must be able to transform to string verbose (bool): Print only if verbose mode trace (bool): Print only if trace mode notify (bool): Run through the notification management system Returns: bool: Message is logged """ return bool(self.TriageMessage( message, additional=additional, verbose=verbose, trace=trace, notify=notify, level=logging.ERROR )) def critical(self, message, additional=None, verbose=False, trace=False, notify=True): """Critical Messages Use this method for logging critical statements Args: message (str): Message to log additional (object): Additional information to log. Note: object must be able to transform to string verbose (bool): Print only if verbose mode trace (bool): Print only if trace mode notify (bool): Run through the notification management system Returns: bool: Message is logged """ return bool(self.TriageMessage( message, additional=additional, verbose=verbose, trace=trace, notify=notify, level=logging.CRITICAL )) def ProvisionLoggers(self): """Loads Log Handler & Config Returns: None: Simple loader, Nothing needed """ if self._conf.get('Logging', 'ConfigurationFile'): if os.path.isfile(self._conf.get('Logging', 'ConfigurationFile')): config.fileConfig(self._conf.get('Logging', 'ConfigurationFile')) self._logger = logging.getLogger('GREASE') else: self.DefaultLogger() else: self.DefaultLogger() def DefaultLogger(self): """Default Logging Provisioning Returns: None: void method to provision class internals """ global GREASE_LOG_HANDLER self._logger = logging.getLogger('GREASE') self._logger.setLevel(logging.DEBUG) self._formatter = logging.Formatter( "{" "\"timestamp\": \"%(asctime)s.%(msecs)03d\", " "\"thread\": \"%(threadName)s\", " "\"level\" : \"%(levelname)s\", " "\"message\" : \"%(message)s\"}", "%Y-%m-%d %H:%M:%S" ) self._formatter.converter = time.gmtime if not GREASE_LOG_HANDLER: if os.path.isdir(self._conf.greaseDir): GREASE_LOG_HANDLER = logging.FileHandler(self._conf.get('Logging', 'file')) GREASE_LOG_HANDLER.setLevel(logging.DEBUG) GREASE_LOG_HANDLER.setFormatter(self._formatter) self._logger.addHandler(GREASE_LOG_HANDLER) else: GREASE_LOG_HANDLER = logging.StreamHandler() GREASE_LOG_HANDLER.setLevel(logging.DEBUG) GREASE_LOG_HANDLER.setFormatter(self._formatter) self._logger.addHandler(GREASE_LOG_HANDLER)
def test_logging_creation_with_conf(self): conf = Configuration() log = Logging(conf) self.assertTrue(isinstance(log, Logging))
class GreaseRouter(object): """Main GREASE CLI Router This class handles routing CLI requests as well as starting the Daemon on Windows/POSIX systems Attributes: _config (Configuration): Main Configuration Object _logger (Logging): Main Logging Instance _importTool (ImportTool): Importer Tool Instance _exit_message (str): Exit Message """ _config = Configuration(os.environ.get('GREASE_CONF', None)) _logger = Logging(_config) _importTool = ImportTool(_logger) _exit_message = None def __init__(self): self._logger.trace("Router Startup", trace=True) def StartGREASE(self): """EntryPoint for CLI scripts for GREASE Returns: None: Void Method for GREASE """ status = self.run() self.exit(status, self._exit_message) def run(self): """Route commands through GREASE Returns: int: Exit Code """ # ensure at least a sub-command has been provided if len(sys.argv) > 1: cmd, context = self.get_arguments() if cmd: # Parse long args to command context if cmd.execute(context): cmd.__del__() del cmd return 0 else: return 3 else: self._exit_message = "Command not found" return 2 else: self._logger.error("Sub-command not provided") self._exit_message = "Sub-command not provided to GREASE CLI" return 1 def exit(self, code, message=None): """Exit program with exit code Args: code (int): Exit Code message (str): Exit message if any Returns: None: Will exit program """ if message: self._logger.info("Message: [{0}]".format(message)) if code != 0: print("ERROR: {0}".format(message)) else: print(message) self._logger.debug("GREASE exit code: [{0}]".format(code), verbose=True) sys.exit(code) def get_arguments(self): """Parse CLI long arguments into dictionaries This expects arguments separated by space `--opt val`, colon `--opt:val`, or equal `--opt=val` signs Returns: object, dict: key->value pairs of arguments """ i = 1 context = {} other = [] cmd = None while i < len(sys.argv): arg = str(sys.argv[i]) if arg.startswith("--"): # Found long opt if len(arg.split("=")) > 1: # was equal separated context[arg.split("=")[0].strip("--")] = arg.split("=")[1] elif len(arg.split(":")) > 1: # was colon separated context[arg.split(":")[0].strip("--")] = arg.split(":")[1] else: if len(sys.argv) < i + 1: # we have a flag rather than an arg context[arg.strip("--")] = True i += 1 elif len(sys.argv) - 1 == i or sys.argv[i + 1].startswith( "--"): # we have a flag rather than an arg context[arg.strip("--")] = True elif sys.argv[i + 1].startswith("--"): # we have a flag rather than an arg context[arg.strip("--")] = True else: # space separated possible_imp = self._importTool.load(sys.argv[i + 1]) if not isinstance(possible_imp, Command): context[arg.strip("--")] = sys.argv[i + 1] else: cmd = possible_imp i += 1 else: possible_imp = self._importTool.load(sys.argv[i]) if isinstance(possible_imp, Command): cmd = possible_imp else: other.append(arg) i += 1 context['grease_other_args'] = other return cmd, context
def test_config_is_false_before_startup(self): self.assertFalse(Configuration.get('MongoDB', 'host'))
def test_scan(self): # setup configList = [ { "name": "test1", "job": "fakeJob", "exe_env": "windows", "source": "TestSource", "logic": { "regex": [ { "field": "character", "pattern": ".*skywalker.*" } ] } } ] ioc = GreaseContainer() ioc.ensureRegistration() ioc.getConfig().set('trace', True, 'Logging') ioc.getConfig().set('verbose', True, 'Logging') fil = open(ioc.getConfig().greaseConfigFile, 'r') data = json.loads(fil.read()) fil.close() fil = open(ioc.getConfig().greaseConfigFile, 'w') data['Import']['searchPath'].append('tgt_grease.enterprise.Model.tests') fil.write(json.dumps(data, sort_keys=True, indent=4)) fil.close() Configuration.ReloadConfig() jServer = ioc.getCollection('JobServer') jID1 = jServer.insert_one({ 'jobs': 0, 'os': platform.system().lower(), 'roles': ["general"], 'prototypes': ["detect"], 'active': True, 'activationTime': datetime.utcnow() }).inserted_id time.sleep(1) jID2 = jServer.insert_one({ 'jobs': 0, 'os': platform.system().lower(), 'roles': ["general"], 'prototypes': ["detect"], 'active': True, 'activationTime': datetime.utcnow() }).inserted_id # Begin Test conf = PrototypeConfig(ioc) conf.load(reloadConf=True, ConfigurationList=configList) scanner = Scan(ioc) # Scan Environment self.assertTrue(scanner.Parse()) # Begin ensuring environment is how we expect # we assert less or equal because sometimes uuid's are close :p self.assertLessEqual(ioc.getCollection('SourceData').find({ 'detectionServer': ObjectId(jID1) }).count(), 3) self.assertLessEqual(ioc.getCollection('SourceData').find({ 'detectionServer': ObjectId(jID2) }).count(), 3) self.assertLessEqual(ioc.getCollection('JobServer').find_one({ '_id': ObjectId(jID1) })['jobs'], 3) self.assertLessEqual(ioc.getCollection('JobServer').find_one({ '_id': ObjectId(jID2) })['jobs'], 3) # clean up fil = open(ioc.getConfig().greaseConfigFile, 'r') data = json.loads(fil.read()) fil.close() # remove collection ioc.getCollection('TestProtoType').drop() # remove prototypes data['NodeInformation']['ProtoTypes'] = [] # pop search path trash = data['Import']['searchPath'].pop() # close out fil = open(ioc.getConfig().greaseConfigFile, 'w') fil.write(json.dumps(data, sort_keys=True, indent=4)) fil.close() jServer.delete_one({'_id': ObjectId(jID1)}) jServer.delete_one({'_id': ObjectId(jID2)}) ioc.getCollection('SourceData').drop() ioc.getCollection('Dedup_Sourcing').drop() ioc.getConfig().set('trace', False, 'Logging') ioc.getConfig().set('verbose', False, 'Logging') Configuration.ReloadConfig()
def test_initialization(self): conf = Configuration() self.assertTrue(conf.get('Connectivity', 'MongoDB'))
class Notifications(object): """Notification Router for third party resources This is the class to handle all notifications to third party resources Attributes: _conf (Configuration): Configuration Object hipchat_url (str): This is the hipchat API url hipchat_token (str): set this to override the config for the auth token hipchat_room (str): set this to override the config for the room """ _conf = None # HipChat Configuration hipchat_url = "https://api.hipchat.com/v2/room/" hipchat_token = None hipchat_room = None def __init__(self, Config=None): if Config and isinstance(Config, Configuration): self._conf = Config else: self._conf = Configuration() def SendMessage(self, message, level=DEBUG, channel=None): """Send Message to configured channels This method is the main point of contact with Notifications in GREASE. This will handle routing to all configured channels. Use `level` to define what level the message is. This can impact whether a message is sent as well as if the message sent will have special attributes (EX: red text). Use `channel` to route around sending to multiple channels if the message traditionally would go to multiple, instead going only to the selected one. Note: if you use the channel argument and the channel is not found you will receive False back Args: message (str): Message to send level (int): Level of message to be sent channel (str): Specific channel to notify Returns: bool: Success of sending """ if channel \ and 'enabled' in self._conf.get('Notifications', channel, {}) \ and self._conf.get('Notifications', channel, {}).get('enabled'): return bool(self._route_notification(channel, message, level)) else: # Capture object for notification channel statuses NotificationStatus = [] NotificationChannels = self._conf.get('Notifications', default={}) # type: dict # Loop through those channels for Notifier, Config in NotificationChannels.items(): # ensure channel is enabled if 'enabled' in Config and Config.get('enabled'): # loop through the channels NotificationStatus.append(bool(self._route_notification(Notifier, message, level))) # make the list unique NotificationStatus = list(set(NotificationStatus)) if len(NotificationStatus) > 1: # we got at least one true and at least one false return False elif len(NotificationStatus) is 1: # return what the categorical state was return bool(NotificationStatus[0]) else: # nothing was configured to run return true return True def _route_notification(self, channel, message, level): """Handle actual calling of notification channels Args: channel (str): Channel to notify message (str): Message to send level (int): Level to send at Returns: bool: Method success status """ if channel == "HipChat": return self.send_hipchat_message(message, level) elif channel == "Slack": return self.send_slack_message(message) else: return False def send_hipchat_message(self, message, level, color=None): """Send a hipchat message Args: message (str): Message to send to hipchat level (int): message level color (str): color of message Returns: bool: API response status """ if not color: if level is DEBUG: color = 'grey' elif level is INFO: color = 'purple' elif level is WARNING: color = 'yellow' elif level is ERROR: color = 'red' elif level is CRITICAL: color = 'red' else: color = 'grey' if self.hipchat_room and self.hipchat_token: url = "{0}{1}/notification?auth_token={2}".format( self.hipchat_url, self.hipchat_room, self.hipchat_token ) else: url = "{0}{1}/notification?auth_token={2}".format( self.hipchat_url, self._conf.get('Notifications', 'HipChat').get('room'), self._conf.get('Notifications', 'HipChat').get('token') ) try: response = requests.post( url=url, data={ 'message': message, 'color': color }, verify=False ) if response.status_code == 204: return True else: return False except HTTPError: return False def send_slack_message(self, message): """Send a slack message to slack channel using webhook url in the configuration Args: message (str): Message to send to Slack Returns: bool: API response status """ webhook_url = self._conf.get('Notifications', 'Slack').get('webhookURL') slack_data = {'text': message} try: response = requests.post( webhook_url, data=json.dumps(slack_data), headers={'Content-Type': 'application/json'} ) return True except HTTPError: return False