From 849c57081bce08d38f480a176d795a46434c71ec Mon Sep 17 00:00:00 2001 From: Walter Gildersleeve Date: Wed, 22 Nov 2023 10:10:31 +0100 Subject: [PATCH] restructured v1 methods --- Integrations/ApiShell/MODULE-README.md | 6 +- Integrations/ApiShell/README.md | 13 +-- Integrations/ApiShell/iriusrisk/__init__.py | 30 +++--- .../ApiShell/iriusrisk/auto_initialize.py | 6 ++ .../ApiShell/iriusrisk/commandline.py | 3 + .../ApiShell/iriusrisk/v1/__init__.py | 99 +++++++++++++++++-- Integrations/ApiShell/iriusrisk/v1/facade.py | 96 ++++-------------- Integrations/ApiShell/pyproject.toml | 2 +- .../OutputLibraryInfo/output-library-info.py | 2 +- 9 files changed, 145 insertions(+), 112 deletions(-) create mode 100644 Integrations/ApiShell/iriusrisk/auto_initialize.py diff --git a/Integrations/ApiShell/MODULE-README.md b/Integrations/ApiShell/MODULE-README.md index b7b3d62..b8c9c64 100644 --- a/Integrations/ApiShell/MODULE-README.md +++ b/Integrations/ApiShell/MODULE-README.md @@ -11,15 +11,15 @@ calls. For further information, install this module locally and run the following: - python3 -c 'from iriusrisk.v1 import *' + python3 -c 'import iriusrisk.auto_initialize' --help This describes in greater detail how to call the program shell. ## Usage example -* Create the file main.py file, consisting of the following: +* Create the file main.py, consisting of the following: + import iriusrisk.auto_initialize from iriusrisk.v1 import * - from iriusrisk.v1.facade import do_get (resp, json) = do_get("products") for i in json: diff --git a/Integrations/ApiShell/README.md b/Integrations/ApiShell/README.md index 08153f3..8d4f38f 100644 --- a/Integrations/ApiShell/README.md +++ b/Integrations/ApiShell/README.md @@ -10,21 +10,16 @@ you to provide them in a file once across multiple script calls. For further information, clone this subdirectory and execute the following: - python3 -c 'import iriusrisk.v1' --help + python3 -c 'import iriusrisk.auto_initialize' --help This describes in greater detail how to call the program shell. -### TODO -* Need to look for ini files in multiple locations -* Need to add a toolkit to ease HTTP calls - -This will describe in detail how to call the program shell. - ## Usage example * clone or branch this repository -* Edit the main.py file, appending the following lines to it: +* Create the file main.py, consisting of the following: - from iriusrisk.v1.facade import do_get + import iriusrisk.auto_initialize + from iriusrisk.v1 import * (resp, json) = do_get("products") for i in json: diff --git a/Integrations/ApiShell/iriusrisk/__init__.py b/Integrations/ApiShell/iriusrisk/__init__.py index eebf21c..78d565b 100644 --- a/Integrations/ApiShell/iriusrisk/__init__.py +++ b/Integrations/ApiShell/iriusrisk/__init__.py @@ -18,20 +18,18 @@ import logging import sys -__all__=["config,parse_arguments,get_commandline_parser"] +__all__=["get_config", "get_commandline_parser", "do_initialization"] -config = None -parse_args_on_load = True +_config_holder = [ None ] _log = logging.getLogger('iriusrisk') _parser = iriusrisk.commandline.get_parser() -"""Call this method before loading any sub-modules of iriusrisk. This then -prevents the automatic parsing of the configuration files and command line, -which is normally done following initialization of the sub-modules. -""" -def suppress_parse_args_on_load(): - parse_args_on_load = False +def get_config(): + if not _config_holder[0]: + raise Exception("Configuration file not initialized. iriusrisk.parse_arguments() must be called first.") + + return _config_holder[0] """This returns the instance of the argparse class used by this method. Use this to add needed command line parameters prior to calling parse_arguments(). @@ -39,12 +37,16 @@ def suppress_parse_args_on_load(): def get_commandline_parser(): return _parser -"""Parse the command line arguments, and load in any config files. By default, -this method is called after any sub-module of iriusrisk is initialized. +"""Parse the command line and load in any initialization files. """ -def parse_arguments(): - global config +def do_initialization(): + global _config_holder + if _config_holder[0]: + _log.info("iriusrisk.parse_arguments() called multiple times") + return + config = _parser.parse_args() + _config_holder[0] = config if config.verbose: logging.basicConfig(level=logging.DEBUG) @@ -82,6 +84,7 @@ def parse_arguments(): _check_url(config.url) def _get_url(config_file): + config = get_config() full_url = _get_item(config_file, config.full_url, "full-url", None) if full_url: _log.info("Using the --full-url option. No URL will be derived from domain or subdomain.") @@ -116,6 +119,7 @@ def _check_url(url): _log.warn("You must supply one of subdomain, domain or url on the command line or in the ini file") _log.warn("Get extended help (--help) from the program for more information") + config = get_config() if not config.dryrun: _log.info("Making a call to the given URL as a fail-fast test") _log.debug("Note that this does not test the security key's validity, but just whether") diff --git a/Integrations/ApiShell/iriusrisk/auto_initialize.py b/Integrations/ApiShell/iriusrisk/auto_initialize.py new file mode 100644 index 0000000..1aa5d04 --- /dev/null +++ b/Integrations/ApiShell/iriusrisk/auto_initialize.py @@ -0,0 +1,6 @@ +"""Loading this module automatically causes the command line to be parsed and +the initialization files to be loaded. +""" +import iriusrisk + +iriusrisk.do_initialization() diff --git a/Integrations/ApiShell/iriusrisk/commandline.py b/Integrations/ApiShell/iriusrisk/commandline.py index d2c48ad..59fe91d 100644 --- a/Integrations/ApiShell/iriusrisk/commandline.py +++ b/Integrations/ApiShell/iriusrisk/commandline.py @@ -57,4 +57,7 @@ def get_parser(): parser.add_argument("-v", "--verbose", help="Output extended log information", action='store_true') parser.add_argument("-q", "--quiet", help="Only output log messages indicating errors", action='store_true') parser.add_argument("--dryrun", help="Do everything but actual HTTP calls", action='store_true') + + parser.add_argument("-k", "--key", help="API Key to use when accessing the v1 API") + # parser.add_argument("-t", "--token", help="OAuth2 Token to use when accessing the v2 API") return parser \ No newline at end of file diff --git a/Integrations/ApiShell/iriusrisk/v1/__init__.py b/Integrations/ApiShell/iriusrisk/v1/__init__.py index 1704b5c..0634fb7 100644 --- a/Integrations/ApiShell/iriusrisk/v1/__init__.py +++ b/Integrations/ApiShell/iriusrisk/v1/__init__.py @@ -1,12 +1,91 @@ -"""This module provides helper methods for accessing IriusRisk API version v1. -It also adds the --key command line parser, which expects the IriusRisk v1 API -key as it's argument. - -Specific methods for calling into the API are provided in the iriusrisk.v1.facade -module. +"""This module contains helper methods for calling the IriusRisk API. """ -import iriusrisk -iriusrisk.get_commandline_parser().add_argument("-k", "--key", help="API Token to use when accessing the API") -if (iriusrisk.parse_args_on_load): - iriusrisk.parse_arguments() \ No newline at end of file +import http.client +import json +import logging +from urllib.parse import quote +from iriusrisk import get_config + +__all__=["do_get","call_endpoint"] + +# Provides helper methods that make accessing the IriusRisk v1 API easier. +_log = logging.getLogger('iriusrisk.v1') + +def _build_path(path, encode_path): + if type(path) is str: + if encode_path: + path = path.split("/") + else: + return path + + if encode_path: + elements = [] + + for element in path: + elements.append(quote(element)) + + path = elements + + return "/".join(path) + +def call_endpoint(path, verb, headers={}, params={}, convert_response=True, encode_path=False): + """Call a named endpoint of the IriusRisk API. + + Arguments: + path : the endpoint path. May be a collection of strings, each element + another call depth. So to call the URL + "/api/v1/products/:productid/threats," you would pass three + elements in the collection, ["products", f"{productid}", "threats"] + verb : the verb when calling the endpoint, for instance GET, PUT, POST etc + headers : (optional) any headers to include on the call + params : (optional) any parameters to include on the call + convert_response: (default: True): whether the response should be converted to JSON + encode_path : (default: False): whether URL encoding should be applied to the + various elements of the path. + + The method returns a tuple containing the HTTP response and data returned as the body + of the response. The type of the data depends on two things. First, if convert_response + is False, plain text is returned. Otherwise, it depends on the return type of the + API call. If (for instance) the return type is "application/json," then a json object + is returned. + """ + path = _build_path(path, encode_path) + _log.info(f"Calling endpoint {path} with verb {verb}") + + config = get_config() + + if not "api-token" in headers: + if config.key: + headers["api-token"] = config.key + else: + _log.info("No API key was provided to this application; API call will likely fail") + + if not "accept" in headers: + headers["accept"] = "application/json" + + path = f"/api/v1/{path}" + _log.debug(f"Making a {verb} call to {path} at {config.url}") + conn = http.client.HTTPSConnection(config.url) + + if config.dryrun: + resp = None + else : + conn.request(verb, path, params, headers) + resp = conn.getresponse() + + result = None + if convert_response and not config.dryrun: + data = resp.read().decode("utf-8") + if resp.status == 200 and headers["accept"] == "application/json": + result = json.loads(data) + else: + result = data + + return (resp, result) + +"""Call the specified endpoint using "GET." +""" +def do_get(path, headers={}, params={}, convert_response=True, encode_path=False): + """Call the indicated endpoint via GET. See call_endpoint for more details.""" + return call_endpoint(path, "GET", headers, params, convert_response, encode_path) \ No newline at end of file diff --git a/Integrations/ApiShell/iriusrisk/v1/facade.py b/Integrations/ApiShell/iriusrisk/v1/facade.py index 12d67de..5008c9b 100644 --- a/Integrations/ApiShell/iriusrisk/v1/facade.py +++ b/Integrations/ApiShell/iriusrisk/v1/facade.py @@ -1,87 +1,33 @@ -"""This module contains helper methods for calling the IriusRisk API. +"""This module is deprecated. Use the methods in the iriusrisk.v1 module instead. """ -import http.client -import json +import iriusrisk import logging -from urllib.parse import quote -from iriusrisk import config -# Provides helper methods that make accessing the IriusRisk v1 API easier. -_log = logging.getLogger('iriusrisk.v1') +_log = logging.getLogger("iriusrisk.v1.facade") -def _build_path(path, encode_path): - if type(path) is str: - if encode_path: - path = path.split("/") - else: - return path +_deprected_do_get_reported = False +_deprected_call_endpoint_reported = False - if encode_path: - elements = [] +_log.warning("""Program is using deprecated module iriusrisk.v1.facade. It needs to use the same-named +methods in iriusrisk.v1 instead. Note that loading this module automatically initializes the application +by loading the init files and parsing the command line arguments. +""") - for element in path: - elements.append(quote(element)) +iriusrisk.do_initialization() - path = elements +def do_get(path, headers={}, params={}, convert_response=True, encode_path=False): + global _deprected_do_get_reported + if not _deprected_do_get_reported: + _log.warning("Calling deprecated method iriusrisk.v1.facade.do_get; call iriusrisk.v1.do_get instead") + _deprected_do_get_reported = True - return "/".join(path) + return iriusrisk.v1.do_get(path, headers, params, convert_response, encode_path) def call_endpoint(path, verb, headers={}, params={}, convert_response=True, encode_path=False): - """Call a named endpoint of the IriusRisk API. - - Arguments: - path : the endpoint path. May be a collection of strings, each element - another call depth. So to call the URL - "/api/v1/products/:productid/threats," you would pass three - elements in the collection, ["products", f"{productid}", "threats"] - verb : the verb when calling the endpoint, for instance GET, PUT, POST etc - headers : (optional) any headers to include on the call - params : (optional) any parameters to include on the call - convert_response: (default: True): whether the response should be converted to JSON - encode_path : (default: False): whether URL encoding should be applied to the - various elements of the path. - - The method returns a tuple containing the HTTP response and data returned as the body - of the response. The type of the data depends on two things. First, if convert_response - is False, plain text is returned. Otherwise, it depends on the return type of the - API call. If (for instance) the return type is "application/json," then a json object - is returned. - """ - path = _build_path(path, encode_path) - _log.info(f"Calling endpoint {path} with verb {verb}") - - if not "api-token" in headers: - if config.key: - headers["api-token"] = config.key - else: - _log.info("No API key was provided to this application; API call will likely fail") - - if not "accept" in headers: - headers["accept"] = "application/json" - - path = f"/api/v1/{path}" - _log.debug(f"Making a {verb} call to {path} at {config.url}") - conn = http.client.HTTPSConnection(config.url) + global _deprected_call_endpoint_reported + if not _deprected_call_endpoint_reported: + _log.warning("Calling deprecated method iriusrisk.v1.facade.call_endpoint; call iriusrisk.v1.call_endpoint instead") + _deprected_call_endpoint_reported = True - if config.dryrun: - resp = None - else : - conn.request(verb, path, params, headers) - resp = conn.getresponse() - - result = None - if convert_response and not config.dryrun: - data = resp.read().decode("utf-8") - if resp.status == 200 and headers["accept"] == "application/json": - result = json.loads(data) - else: - result = data - - return (resp, result) - -"""Call the specified endpoint using "GET." -""" -def do_get(path, headers={}, params={}, convert_response=True, encode_path=False): - """Call the indicated endpoint via GET. See call_endpoint for more details.""" - return call_endpoint(path, "GET", headers, params, convert_response, encode_path) \ No newline at end of file + return iriusrisk.v1.call_endpoint(path, verb, headers, params, convert_response, encode_path) \ No newline at end of file diff --git a/Integrations/ApiShell/pyproject.toml b/Integrations/ApiShell/pyproject.toml index a9a0add..6363a00 100644 --- a/Integrations/ApiShell/pyproject.toml +++ b/Integrations/ApiShell/pyproject.toml @@ -7,7 +7,7 @@ includes=["iriusrisk/"] [project] name = "iriusrisk_apishell_v1" -version = "0.3.1" +version = "0.3.2" authors = [ { name="Walter Gildersleeve", email="wgildersleeve@iriusrisk.com"}, ] diff --git a/Integrations/OutputLibraryInfo/output-library-info.py b/Integrations/OutputLibraryInfo/output-library-info.py index 9822196..d0c047b 100644 --- a/Integrations/OutputLibraryInfo/output-library-info.py +++ b/Integrations/OutputLibraryInfo/output-library-info.py @@ -16,8 +16,8 @@ # # python3 -c 'from iriusrisk.v1 import *' --help # +import iriusrisk.auto_initialize from iriusrisk.v1 import * -from iriusrisk.v1.facade import do_get import datetime import logging