From 38eb9f2a77d2d6ed9ec5016ceaa53b2ea6d9e68e Mon Sep 17 00:00:00 2001 From: Rahul Nair Date: Wed, 1 Feb 2017 10:40:53 -0600 Subject: [PATCH] Adding i18n support to Syntribos Adding i18n support to syntribos log messages and prompts. Change-Id: If9914447ccbf3ac2f9c88c3460756f4b682630ec --- babel.cfg | 3 + pylintrc | 2 +- requirements.txt | 1 + setup.cfg | 17 ++ syntribos/_i18n.py | 58 ++++++ syntribos/clients/http/debug_logger.py | 21 ++- syntribos/clients/http/parser.py | 32 ++-- syntribos/config.py | 186 ++++++++++++-------- syntribos/result.py | 2 +- syntribos/runner.py | 80 +++++---- syntribos/signal.py | 12 +- syntribos/tests/fuzz/buffer_overflow.py | 9 +- syntribos/tests/fuzz/command_injection.py | 11 +- syntribos/tests/fuzz/integer_overflow.py | 9 +- syntribos/tests/fuzz/json_depth_overflow.py | 9 +- syntribos/tests/fuzz/sql.py | 9 +- syntribos/tests/fuzz/user_defined.py | 11 +- syntribos/tests/headers/cors.py | 7 +- syntribos/tests/headers/xst.py | 12 +- syntribos/tests/transport_layer/ssl.py | 7 +- syntribos/utils/cleanup.py | 2 +- syntribos/utils/env.py | 84 +++++---- syntribos/utils/remotes.py | 13 +- 23 files changed, 380 insertions(+), 217 deletions(-) create mode 100644 babel.cfg create mode 100644 syntribos/_i18n.py diff --git a/babel.cfg b/babel.cfg new file mode 100644 index 00000000..ea2b448f --- /dev/null +++ b/babel.cfg @@ -0,0 +1,3 @@ +# Extraction from Python source files +[python: **.py] +encoding = utf-8 diff --git a/pylintrc b/pylintrc index 74ab64d8..355802e1 100644 --- a/pylintrc +++ b/pylintrc @@ -16,7 +16,7 @@ extension-pkg-whitelist= disable=all -enable=bad-indentation,bad-builtin,pointless-statement,bad-continuation,unidiomatic-typecheck,method-hidden,lost-exception,attribute-defined-outside-init,expression-not-assigned,anomalous-backslash-in-string,wildcard-import,unreachable,unused-variable,blacklisted-name,logging-format-interpolation,cylic-import +enable=bad-indentation,bad-builtin,pointless-statement,bad-continuation,unidiomatic-typecheck,method-hidden,lost-exception,attribute-defined-outside-init,expression-not-assigned,anomalous-backslash-in-string,wildcard-import,unreachable,blacklisted-name,logging-format-interpolation,cylic-import [REPORTS] output-format=text reports=yes diff --git a/requirements.txt b/requirements.txt index 890978d0..709056f2 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,7 @@ # The order of packages is significant, because pip processes them in the order # of appearance. Changing the order has an impact on the overall integration # process, which may cause wedges in the gate later. +oslo.i18n>=2.1.0 # Apache-2.0 six>=1.9.0 # MIT requests!=2.12.2,!=2.13.0,>=2.10.0 # Apache-2.0 oslo.config!=3.18.0,>=3.14.0 # Apache-2.0 diff --git a/setup.cfg b/setup.cfg index 124bd3e0..f8f3e60d 100644 --- a/setup.cfg +++ b/setup.cfg @@ -36,3 +36,20 @@ oslo.config.opts = all_files = 1 build-dir = doc/build source-dir = doc/source + +[files] +packages = syntribos + +[compile_catalog] +directory = syntribos/locale +domain = syntribos + +[update_catalog] +domain = syntribos +output_dir = syntribos/locale +input_file = syntribos/locale/syntribos.pot + +[extract_messages] +keywords = _ gettext ngettext l_ lazy_gettext +mapping_file = babel.cfg +output_file = syntribos/locale/syntribos.pot \ No newline at end of file diff --git a/syntribos/_i18n.py b/syntribos/_i18n.py new file mode 100644 index 00000000..b7a78387 --- /dev/null +++ b/syntribos/_i18n.py @@ -0,0 +1,58 @@ +# Copyright 2017 Intel +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +"""oslo.i18n integration module. + +See http://docs.openstack.org/developer/oslo.i18n/usage.html . + +""" + +import oslo_i18n + +DOMAIN = 'syntribos' + +_translators = oslo_i18n.TranslatorFactory(domain=DOMAIN) + +# The primary translation function using the well-known name "_" +_ = _translators.primary + +# The contextual translation function using the name "_C" +# requires oslo.i18n >=2.1.0 +_C = _translators.contextual_form + +# The plural translation function using the name "_P" +# requires oslo.i18n >=2.1.0 +_P = _translators.plural_form + +# Translators for log levels. +# +# The abbreviated names are meant to reflect the usual use of a short +# name like '_'. The "L" is for "log" and the other letter comes from +# the level. +_LI = _translators.log_info +_LW = _translators.log_warning +_LE = _translators.log_error +_LC = _translators.log_critical + + +def enable_lazy(): + return oslo_i18n.enable_lazy() + + +def translate(value, user_locale): + return oslo_i18n.translate(value, user_locale) + + +def get_available_languages(): + return oslo_i18n.get_available_languages(DOMAIN) diff --git a/syntribos/clients/http/debug_logger.py b/syntribos/clients/http/debug_logger.py index 89e381de..17c296fd 100644 --- a/syntribos/clients/http/debug_logger.py +++ b/syntribos/clients/http/debug_logger.py @@ -23,6 +23,7 @@ import requests import six import syntribos.checks.http as http_checks +from syntribos._i18n import _, _LC, _LI # noqa import syntribos.signal from syntribos.utils import string_utils @@ -69,8 +70,9 @@ def log_http_transaction(log, level=logging.DEBUG): except Exception as exception: # Ignore all exceptions that happen in logging, then log them log.info( - 'Exception occurred while logging signature of calling' - 'method in http client') + _LI( + 'Exception occurred while logging signature of calling' + 'method in http client')) log.exception(exception) # Make the request and time its execution @@ -82,11 +84,12 @@ def log_http_transaction(log, level=logging.DEBUG): response = func(*args, **kwargs) except requests.exceptions.RequestException as exc: signals.register(http_checks.check_fail(exc)) - log.log(level, "A call to request() failed.") + log.log(level, _("A call to request() failed.")) log.exception(exc) log.log(level, "=" * 80) except Exception as exc: - log.critical('Call to Requests failed due to exception') + log.critical(_LC( + 'Call to Requests failed due to exception')) log.exception(exc) signals.register(syntribos.signal.from_generic_exception(exc)) raise exc @@ -94,8 +97,9 @@ def log_http_transaction(log, level=logging.DEBUG): if len(signals) > 0 and response is None: no_resp_time = time() - start log.log(level, - 'Request failed, elapsed time....: {0:.5f} sec.\n'. - format(no_resp_time)) + _( + 'Request failed, elapsed time....: %.6f sec.\n' + ), no_resp_time) return (response, signals) # requests lib 1.0.0 renamed body to data in the request object @@ -106,8 +110,9 @@ def log_http_transaction(log, level=logging.DEBUG): request_body = response.request.data else: log.info( - "Unable to log request body, neither a 'data' nor a " - "'body' object could be found") + _LI( + "Unable to log request body, neither a 'data' nor a " + "'body' object could be found")) # requests lib 1.0.4 removed params from response.request request_params = '' diff --git a/syntribos/clients/http/parser.py b/syntribos/clients/http/parser.py index e32805e9..de7e6c3c 100644 --- a/syntribos/clients/http/parser.py +++ b/syntribos/clients/http/parser.py @@ -13,6 +13,7 @@ # limitations under the License. import ast import copy +from functools import reduce import importlib import json import re @@ -27,6 +28,8 @@ import six from six.moves import html_parser from six.moves.urllib import parse as urlparse +from syntribos._i18n import _, _LE, _LW # noqa + CONF = cfg.CONF _iterators = {} _string_var_objs = {} @@ -114,23 +117,25 @@ class RequestCreator(object): try: return reduce(getattr, var_obj.val.split("."), CONF) except AttributeError: - print("Meta json file contains reference to the config option " - "{0}, which does not appear to exist.".format( - var_obj.val)) + print(_( + "Meta json file contains reference to the config option " + "%s, which does not appear to exist.") % var_obj.val) elif var_obj.var_type == 'function': if var_obj.function_return_value: return var_obj.function_return_value if not var_obj.val: - print("The type of variable {0} is function, but there is no " - "reference to the function.") + print(_( + "The type of variable is function, but there is no " + "reference to the function.")) return var_obj.function_return_value = cls.call_one_external_function( var_obj.val, var_obj.args) return var_obj.function_return_value elif var_obj.var_type == 'generator': if not var_obj.val: - print("The type of variable {0} is generator, but there is no " - "reference to the function.") + print(_( + "The type of variable {0} is generator, but there is no " + "reference to the function.")) return return cls.call_one_external_function(var_obj.val, var_obj.args) else: @@ -247,7 +252,7 @@ class RequestCreator(object): data = ElementTree.fromstring(data) except Exception: if not re.match(postdat_regex, data): - raise TypeError("Unknown data format") + raise TypeError(_("Unknown data format")) return data @classmethod @@ -291,8 +296,8 @@ class RequestCreator(object): match = re.search(cls.FUNC_WITH_ARGS, string) func_string_has_args = True if not match: - print("The reference to the function {0} failed to parse " - "correctly".format(string)) + print(_("The reference to the function %s failed to parse " + "correctly") % string) return dot_path = match.group(1) func_name = match.group(2) @@ -320,8 +325,10 @@ class VariableObject(object): def __init__(self, name, var_type="", args=[], val="", fuzz=True, fuzz_type="", min_length=0, max_length=sys.maxsize, **kwargs): if var_type and var_type.lower() not in self.VAR_TYPES: - print("The variable {0} has a type of {1} which syntribos does not" - " recognize".format(name, var_type)) + print(_( + "The variable %(name)s has a type of %(var)s which" + " syntribos does not" + " recognize") % {'name': name, 'var': var_type}) self.name = name self.var_type = var_type.lower() self.val = val @@ -338,6 +345,7 @@ class VariableObject(object): class RequestHelperMixin(object): """Class that helps with fuzzing requests.""" + def __init__(self): self.data = "" self.headers = "" diff --git a/syntribos/config.py b/syntribos/config.py index 4a64f908..07e5e7b6 100644 --- a/syntribos/config.py +++ b/syntribos/config.py @@ -18,6 +18,7 @@ import sys from oslo_config import cfg import syntribos +from syntribos._i18n import _, _LE, _LW # noqa from syntribos.utils.file_utils import ContentType from syntribos.utils.file_utils import ExistingDirType @@ -42,9 +43,9 @@ def handle_config_exception(exc): if CONF._args[0] == "init": return - msg = ("Configuration file specified ('{config}') wasn't " - "found or was unreadable.").format(config=",".join( - CONF.config_file)) + msg = (_("Configuration file specified ('%s') wasn't " + "found or was unreadable.") % ",".join( + CONF.config_file)) if msg: LOG.warning(msg) @@ -64,42 +65,48 @@ remote_group = cfg.OptGroup(name="remote", title="Remote config") def sub_commands(sub_parser): init_parser = sub_parser.add_parser( "init", - help="Initialize syntribos environment after " - "installation. Should be run before any other " - "commands.") + help=_("Initialize syntribos environment after " + "installation. Should be run before any other " + "commands.")) init_parser.add_argument( "--force", dest="force", action="store_true", - help="Skip prompts for configurable options, force initialization " - "even if syntribos believes it has already been initialized. If " - "--custom_install_root isn't specified, we will use the default " - "options. WARNING: This is potentially destructive! Use with caution.") + help=_( + "Skip prompts for configurable options, force initialization " + "even if syntribos believes it has already been initialized. If " + "--custom_install_root isn't specified, we will use the default " + "options. WARNING: This is potentially destructive! Use with " + "caution.")) init_parser.add_argument( "--custom_install_root", dest="custom_install_root", - help="Skip prompts for configurable options, and initialize syntribos " - "in the specified directory. Can be combined with --force to " - "overwrite existing files.") + help=_("Skip prompts for configurable options, and initialize " + "syntribos in the specified directory. Can be combined " + "with --force to overwrite existing files.")) init_parser.add_argument( "--no_downloads", dest="no_downloads", action="store_true", - help="Disable the downloading of payload files as part of the " - "initialization process") + help=_("Disable the downloading of payload files as part of the " + "initialization process")) download_parser = sub_parser.add_parser( "download", - help="Download payload and template files. This command is " - "configurable according to the remote section of your config file") + help=_( + "Download payload and template files. This command is " + "configurable according to the remote section of your " + "config file")) download_parser.add_argument( "--templates", dest="templates", action="store_true", - help="Download templates") + help=_("Download templates")) download_parser.add_argument( "--payloads", dest="payloads", action="store_true", - help="Download payloads") + help=_("Download payloads")) sub_parser.add_parser("list_tests", - help="List all available tests") + help=_("List all available tests")) sub_parser.add_parser("run", - help="Run syntribos with given config options") + help=_("Run syntribos with given config" + "options")) sub_parser.add_parser("dry_run", - help="Dry run syntribos with given config options") + help=_("Dry run syntribos with given config" + "options")) def list_opts(): @@ -140,28 +147,32 @@ def list_cli_opts(): return [ cfg.SubCommandOpt(name="sub_command", handler=sub_commands, - help="Available commands", + help=_("Available commands"), title="syntribos Commands"), cfg.MultiStrOpt("test-types", dest="test_types", short="t", default=[""], sample_default=["SQL", "XSS"], - help="Test types to run against the target API"), + help=_( + "Test types to run against the target API")), cfg.MultiStrOpt("excluded-types", dest="excluded_types", short="e", default=[""], sample_default=["SQL", "XSS"], - help="Test types to be excluded from current run" - "against the target API"), + help=_("Test types to be excluded from " + "current run against the target API")), cfg.BoolOpt("colorize", dest="colorize", short="cl", default=False, - help="Enable color in syntribos terminal output"), + help=_("Enable color in syntribos terminal output")), cfg.StrOpt("outfile", short="o", - sample_default="out.json", help="File to print output to"), + sample_default="out.json", help=_("File to print " + "output to")), cfg.StrOpt("format", dest="output_format", short="f", default="json", choices=["json"], ignore_case=True, - help="The format for outputting results"), + help=_("The format for outputting results")), cfg.StrOpt("min-severity", dest="min_severity", short="S", default="LOW", choices=syntribos.RANKING, - help="Select a minimum severity for reported defects"), + help=_("Select a minimum severity for reported " + "defects")), cfg.StrOpt("min-confidence", dest="min_confidence", short="C", default="LOW", choices=syntribos.RANKING, - help="Select a minimum confidence for reported defects") + help=_("Select a minimum confidence for reported " + "defects")) ] @@ -171,63 +182,77 @@ def list_syntribos_opts(): try: func(*args) except IOError: - print("\nCan't open a file or directory specified in the " - "config file under the section `[syntribos]`; verify " - "if the path exists.\nFor more information please refer " - "the debug logs.") + msg = _( + "\nCan't open a file or directory specified in the " + "config file under the section `[syntribos]`; verify " + "if the path exists.\nFor more information please refer " + "the debug logs.") + print(msg) exit(1) return wrap return [ cfg.StrOpt("endpoint", default="", sample_default="http://localhost/app", - help="The target host to be tested"), + help=_("The target host to be tested")), cfg.Opt("templates", type=ContentType("r", 0), default="", sample_default="~/.syntribos/templates", - help="A directory of template files, or a single template " - "file, to test on the target API"), + help=_("A directory of template files, or a single " + "template file, to test on the target API")), cfg.StrOpt("payloads", default="", sample_default="~/.syntribos/data", - help="The location where we can find syntribos'" - "payloads"), + help=_( + "The location where we can find syntribos'" + "payloads")), cfg.MultiStrOpt("exclude_results", default=[""], sample_default=["500_errors", "length_diff"], - help="Defect types to exclude from the " - "results output"), + help=_( + "Defect types to exclude from the " + "results output")), cfg.Opt("custom_root", type=wrap_try_except(ExistingDirType()), short="c", sample_default="/your/custom/root", - help="The root directory where the subfolders that make up" - " syntribos' environment (logs, templates, payloads, " - "configuration files, etc.)"), + help=_( + "The root directory where the subfolders that make up" + " syntribos' environment (logs, templates, payloads, " + "configuration files, etc.)")), ] def list_user_opts(): return [ cfg.StrOpt("version", default="v2.0", - help="keystone version", choices=["v2.0", "v3"]), - cfg.StrOpt("username", default="", help="keystone username"), - cfg.StrOpt("password", default="", help="keystone user password", + help=_("keystone version"), choices=["v2.0", "v3"]), + cfg.StrOpt("username", default="", + help=_("keystone username")), + cfg.StrOpt("password", default="", + help=_("keystone user password"), secret=True), cfg.StrOpt("user_id", default="", - help="Keystone user ID", secret=True), - cfg.StrOpt("token", default="", help="keystone auth token", + help=_("Keystone user ID"), secret=True), + cfg.StrOpt("token", default="", help=_("keystone auth token"), secret=True), - cfg.StrOpt("endpoint", default="", help="keystone endpoint URI"), - cfg.StrOpt("domain_name", default="", help="keystone domain name"), - cfg.StrOpt("project_id", default="", help="keystone project id"), - cfg.StrOpt("project_name", default="", help="keystone project name"), - cfg.StrOpt("domain_id", default="", help="keystone domain id"), - cfg.StrOpt("tenant_name", default="", help="keystone tenant name"), - cfg.StrOpt("tenant_id", default="", help="keystone tenant id"), + cfg.StrOpt("endpoint", default="", + help=_("keystone endpoint URI")), + cfg.StrOpt("domain_name", default="", + help=_("keystone domain name")), + cfg.StrOpt("project_id", default="", + help=_("keystone project id")), + cfg.StrOpt("project_name", default="", + help=_("keystone project name")), + cfg.StrOpt("domain_id", default="", + help=_("keystone domain id")), + cfg.StrOpt("tenant_name", default="", + help=_("keystone tenant name")), + cfg.StrOpt("tenant_id", default="", + help=_("keystone tenant id")), cfg.StrOpt("serialize_format", default="json", - help="Type of request body"), + help=_("Type of request body")), cfg.StrOpt("deserialize_format", default="json", - help="Type of response body"), + help=_("Type of response body")), cfg.IntOpt("token_ttl", default=1800, - help="Time to live for token in seconds") + help=_("Time to live for token in seconds")) ] @@ -235,19 +260,24 @@ def list_user_opts(): def list_test_opts(): return [ cfg.FloatOpt("length_diff_percent", default=1000.0, - help="Percentage difference between initial request " - "and test request body length to trigger a signal"), + help=_( + "Percentage difference between initial request " + "and test request body length to trigger a signal")), cfg.FloatOpt("time_diff_percent", default=1000.0, - help="Percentage difference between initial response " - "time and test response time to trigger a signal"), + help=_( + "Percentage difference between initial response " + "time and test response time to trigger a signal")), cfg.IntOpt("max_time", default=10, - help="Maximum absolute time (in seconds) to wait for a " - "response before triggering a timeout signal"), + help=_( + "Maximum absolute time (in seconds) to wait for a " + "response before triggering a timeout signal")), cfg.IntOpt("max_length", default=500, - help="Maximum length (in characters) of the response text"), + help=_( + "Maximum length (in characters) of the response text")), cfg.ListOpt("failure_keys", default="[`syntax error`]", - help="Comma seperated list of keys for which the test " - "would fail.") + help=_( + "Comma seperated list of keys for which the test " + "would fail.")) ] @@ -255,11 +285,14 @@ def list_logger_opts(): # TODO(unrahul): Add log formating and verbosity options return [ cfg.BoolOpt("http_request_compression", default=True, - help="Request content compression to compress fuzz " - "strings present in the http request content."), + help=_( + "Request content compression to compress fuzz " + "strings present in the http request content.")), cfg.StrOpt("log_dir", default="", sample_default="~/.syntribos/logs", - help="Where to save debug log files for a syntribos run") + help=_( + "Where to save debug log files for a syntribos run" + )) ] @@ -269,17 +302,18 @@ def list_remote_opts(): cfg.StrOpt( "cache_dir", default="", - help="Base directory where cached files can be saved"), + help=_("Base directory where cached files can be saved")), cfg.StrOpt( "payloads_uri", default=("https://github.com/openstack/syntribos-payloads/" "archive/master.tar.gz"), - help="Remote URI to download payloads."), + help=_("Remote URI to download payloads.")), cfg.StrOpt( "templates_uri", default=("https://github.com/rahulunair/openstack-templates/" "archive/master.tar.gz"), - help="Remote URI to download templates."), + help=_("Remote URI to download templates.")), cfg.BoolOpt("enable_cache", default=True, - help="Cache remote template & payload resources locally"), + help=_( + "Cache remote template & payload resources locally")), ] diff --git a/syntribos/result.py b/syntribos/result.py index 74b07969..f2138297 100644 --- a/syntribos/result.py +++ b/syntribos/result.py @@ -254,5 +254,5 @@ class IssueTestResult(unittest.TextTestResult): esuff="s" * bool(num_err - 1))) if test_log: print(syntribos.SEP) - print("LOG PATH...: {path}".format(path=test_log)) + print(syntribos._("LOG PATH...: %s") % test_log) print(syntribos.SEP) diff --git a/syntribos/runner.py b/syntribos/runner.py index 20918229..3114527d 100644 --- a/syntribos/runner.py +++ b/syntribos/runner.py @@ -25,6 +25,7 @@ from six.moves import input import syntribos.config from syntribos.formatters.json_formatter import JSONFormatter +from syntribos._i18n import _, _LW, _LE # noqa import syntribos.result import syntribos.tests as tests import syntribos.tests.base @@ -54,14 +55,15 @@ class Runner(object): @classmethod def list_tests(cls): """Print out the list of available tests types that can be run.""" - print("List of available tests...:\n") - print("{:<50}{}\n".format("[Test Name]", "[Description]")) + print(_("List of available tests...:\n")) + print("{:<50}{}\n".format(_("[Test Name]"), + _("[Description]"))) testdict = {name: clss.__doc__ for name, clss in cls.get_tests()} for test in sorted(testdict): if testdict[test] is None: raise Exception( - ("No test description provided" - " as doc string for the test: {0}".format(test))) + _("No test description provided" + " as doc string for the test: %s") % test) else: test_description = testdict[test].split(".")[0] print("{test:<50}{desc}\r".format( @@ -74,7 +76,7 @@ class Runner(object): :param package: a package of tests for pkgutil to load """ - for _, modname, _ in pkgutil.walk_packages( + for i, modname, k in pkgutil.walk_packages( path=package.__path__, prefix=package.__name__ + '.', onerror=lambda x: None): @@ -187,14 +189,16 @@ class Runner(object): ENV.download_wrapper() exit(0) except AttributeError: - print("Not able to run the requested sub command, please check " - "the debug logs for more information, exiting...") + print( + _( + "Not able to run the requested sub command, please check " + "the debug logs for more information, exiting...")) exit(1) if not ENV.is_syntribos_initialized(): - print("Syntribos was not initialized. Please run the 'init' " - "command or set it up manually. See the README for more " - "information about the installation process.") + print(_("Syntribos was not initialized. Please run the 'init'" + " command or set it up manually. See the README for" + " more information about the installation process.")) exit(1) cls.setup_runtime_env() @@ -210,20 +214,20 @@ class Runner(object): dry_run_output = {"failures": [], "successes": []} list_of_tests = list(cls.get_tests(dry_run=True)) - print("\nRunning Tests...:") + print(_("\nRunning Tests...:")) templates_dir = CONF.syntribos.templates if templates_dir is None: - print("Attempting to download templates from {}".format( + print(_("Attempting to download templates from {}").format( CONF.remote.templates_uri)) templates_path = remotes.get(CONF.remote.templates_uri) try: templates_dir = ContentType("r", 0)(templates_path) except IOError: - print("Not able to open `{}`; " - "please verify path, exiting...".format(templates_path)) + print(_("Not able to open `%s`; please verify path, " + "exiting...") % templates_path) exit(1) - print("\nPress Ctrl-C to pause or exit...\n") + print(_("\nPress Ctrl-C to pause or exit...\n")) # TODO(mdong): Make this handle inheritence and all that. For now, just # pass the first meta file it sees to the parser. Also, find a better # way to pass meta_vars @@ -242,12 +246,12 @@ class Runner(object): LOG = cls.get_logger(file_path) CONF.log_opt_values(LOG, logging.DEBUG) if not file_path.endswith(".template"): - LOG.debug( - 'file.......: %s (SKIPPED - not a .template file)', + LOG.warning( + _LW('file.....:%s (SKIPPED - not a .template file)'), file_path) continue - test_names = [t for (t, _) in list_of_tests] + test_names = [t for (t, i) in list_of_tests] # noqa log_string = ''.join([ '\n{0}\nTEMPLATE FILE\n{0}\n'.format('-' * 12), 'file.......: {0}\n'.format(file_path), @@ -288,20 +292,20 @@ class Runner(object): :return: None """ - for _, test_class in list_of_tests: + for k, test_class in list_of_tests: # noqa try: print("\nParsing template file...") test_class.create_init_request(file_path, req_str, meta_vars) except Exception as e: print("Error in parsing template:\n \t{0}\n".format( traceback.format_exc())) - LOG.exception("Error in parsing template:") + LOG.error(_LE("Error in parsing template:")) output["failures"].append({ "file": file_path, "error": e.__str__() }) else: - print("Request sucessfully generated!\n") + print(_("Request sucessfully generated!\n")) output["successes"].append(file_path) test_cases = list(test_class.get_test_cases(file_path, req_str)) @@ -319,7 +323,7 @@ class Runner(object): test_log = cls.log_path print(syntribos.SEP) - print("LOG PATH...: {path}".format(path=test_log)) + print(_("LOG PATH...: {path}").format(path=test_log)) print(syntribos.SEP) @classmethod @@ -358,9 +362,10 @@ class Runner(object): try: test_class.send_init_request(file_path, req_str, meta_vars) except Exception: - print("Error in parsing template:\n \t{0}\n".format( - traceback.format_exc())) - LOG.exception("Error in parsing template:") + print(_( + "Error in parsing template:\n %s\n" + ) % traceback.format_exc()) + LOG.error(_LE("Error in parsing template:")) break test_cases = list( test_class.get_test_cases(file_path, req_str)) @@ -390,35 +395,38 @@ class Runner(object): last_failures = result.stats["failures"] last_errors = result.stats["errors"] errors = cli.colorize(errors, "red") - print(" : {0} Failure(s), {1} Error(s)\r".format( - failures, errors)) + print(_( + " : %(fail)s Failure(s), %(err) Error(s)\r") % { + "fail": failures, "err": errors}) else: last_failures = result.stats["failures"] - print(" : {} Failure(s), 0 Error(s)\r".format( - failures)) + print( + _( + " : %s Failure(s), 0 Error(s)\r") % failures) run_time = time.time() - template_start_time - LOG.debug("Run time: %s sec.", run_time) + LOG.info(_("Run time: %s sec."), run_time) if hasattr(result, "testsRun"): num_tests = result.testsRun - result.testsRunSinceLastPrint - print("\nRan {num} test(s) in {time:.3f}s\n".format( - num=num_tests, time=run_time)) + print(_("\nRan %(num)s test(s) in %.3(time)f s\n") % + {"num": num_tests, "time": run_time}) result.testsRunSinceLastPrint = result.testsRun except KeyboardInterrupt: - print('\n\nPausing... Hit ENTER to continue, type quit to exit.') + print(_( + '\n\nPausing...Hit ENTER to continue, type quit to exit.')) try: response = input() if response.lower() == "quit": result.print_result(cls.start_time) cleanup.delete_temps() - print("Exiting...") + print(_("Exiting...")) exit(0) - print('Resuming...') + print(_('Resuming...')) except KeyboardInterrupt: result.print_result(cls.start_time) cleanup.delete_temps() - print("Exiting...") + print(_("Exiting...")) exit(0) @classmethod diff --git a/syntribos/signal.py b/syntribos/signal.py index e7914aea..83365ee8 100644 --- a/syntribos/signal.py +++ b/syntribos/signal.py @@ -13,6 +13,8 @@ # limitations under the License. import six +from syntribos._i18n import _, _LE, _LW # noqa + class SignalHolder(object): """SignalHolder represents a 'set' of SynSignals. @@ -247,14 +249,14 @@ def from_generic_exception(exception): :returns: A signal describing the exception """ if not isinstance(exception, Exception): - raise Exception("This function accepts only Exception objects") + raise Exception(_("This function accepts only Exception objects")) exc_text = str(exception) - text = "This request raised an exception: '{0}'".format(exc_text) + text = _("This request raised an exception: '%s'") % exc_text data = { - "exception_name": exception.__class__.__name__, - "exception_text": exc_text, - "exception": exception + _("exception_name"): exception.__class__.__name__, + _("exception_text"): exc_text, + _("exception"): exception } slug = "GENERIC_EXCEPTION_{name}".format( name=data["exception_name"].upper()) diff --git a/syntribos/tests/fuzz/buffer_overflow.py b/syntribos/tests/fuzz/buffer_overflow.py index 03e05afe..25f45ba6 100644 --- a/syntribos/tests/fuzz/buffer_overflow.py +++ b/syntribos/tests/fuzz/buffer_overflow.py @@ -12,6 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. import syntribos +from syntribos._i18n import _ from syntribos.checks import has_string as has_string from syntribos.checks import time_diff as time_diff from syntribos.tests.fuzz import base_fuzz @@ -60,10 +61,10 @@ class BufferOverflowBody(base_fuzz.BaseFuzzTestCase): defect_type="bof_timing", severity=syntribos.MEDIUM, confidence=syntribos.MEDIUM, - description=("The time it took to resolve a request with a " - "long string was too long compared to the " - "baseline request. This could indicate a " - "vulnerability to buffer overflow attacks")) + description=(_("The time it took to resolve a request with a " + "long string was too long compared to the " + "baseline request. This could indicate a " + "vulnerability to buffer overflow attacks"))) class BufferOverflowParams(BufferOverflowBody): diff --git a/syntribos/tests/fuzz/command_injection.py b/syntribos/tests/fuzz/command_injection.py index 35c36cb5..1f5253b5 100644 --- a/syntribos/tests/fuzz/command_injection.py +++ b/syntribos/tests/fuzz/command_injection.py @@ -13,6 +13,7 @@ # limitations under the License. import syntribos +from syntribos._i18n import _ from syntribos.checks import has_string as has_string from syntribos.checks import time_diff as time_diff from syntribos.tests.fuzz import base_fuzz @@ -51,11 +52,11 @@ class CommandInjectionBody(base_fuzz.BaseFuzzTestCase): defect_type="command_injection", severity=syntribos.HIGH, confidence=syntribos.MEDIUM, - description=("The time elapsed between the sending of " - "the request and the arrival of the res" - "ponse exceeds the expected amount of time, " - "suggesting a vulnerability to command " - "injection attacks.")) + description=(_("The time elapsed between the sending of " + "the request and the arrival of the res" + "ponse exceeds the expected amount of time, " + "suggesting a vulnerability to command " + "injection attacks."))) class CommandInjectionParams(CommandInjectionBody): diff --git a/syntribos/tests/fuzz/integer_overflow.py b/syntribos/tests/fuzz/integer_overflow.py index 1c33c448..18fcb322 100644 --- a/syntribos/tests/fuzz/integer_overflow.py +++ b/syntribos/tests/fuzz/integer_overflow.py @@ -12,6 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. import syntribos +from syntribos._i18n import _ from syntribos.checks import time_diff as time_diff from syntribos.tests.fuzz import base_fuzz @@ -30,10 +31,10 @@ class IntOverflowBody(base_fuzz.BaseFuzzTestCase): defect_type="int_timing", severity=syntribos.MEDIUM, confidence=syntribos.MEDIUM, - description=("The time it took to resolve a request with an " - "invalid integer was too long compared to the " - "baseline request. This could indicate a " - "vulnerability to buffer overflow attacks")) + description=(_("The time it took to resolve a request with an " + "invalid integer was too long compared to the " + "baseline request. This could indicate a " + "vulnerability to buffer overflow attacks"))) class IntOverflowParams(IntOverflowBody): diff --git a/syntribos/tests/fuzz/json_depth_overflow.py b/syntribos/tests/fuzz/json_depth_overflow.py index 11af2b70..e57661fb 100644 --- a/syntribos/tests/fuzz/json_depth_overflow.py +++ b/syntribos/tests/fuzz/json_depth_overflow.py @@ -12,6 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. import syntribos +from syntribos._i18n import _ from syntribos.checks import has_string as has_string from syntribos.checks import time_diff as time_diff from syntribos.tests.fuzz import base_fuzz @@ -56,7 +57,7 @@ class JSONDepthOverflowBody(base_fuzz.BaseFuzzTestCase): defect_type="json_depth_timing", severity=syntribos.MEDIUM, confidence=syntribos.MEDIUM, - description=("The time it took to resolve a request " - "was too long compared to the " - "baseline request. This could indicate a " - "vulnerability to denial of service attacks.")) + description=(_("The time it took to resolve a request " + "was too long compared to the " + "baseline request. This could indicate a " + "vulnerability to denial of service attacks."))) diff --git a/syntribos/tests/fuzz/sql.py b/syntribos/tests/fuzz/sql.py index d32dc60a..0765c762 100644 --- a/syntribos/tests/fuzz/sql.py +++ b/syntribos/tests/fuzz/sql.py @@ -12,6 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. import syntribos +from syntribos._i18n import _ from syntribos.checks import has_string as has_string from syntribos.checks import time_diff as time_diff from syntribos.tests.fuzz import base_fuzz @@ -54,10 +55,10 @@ class SQLInjectionBody(base_fuzz.BaseFuzzTestCase): defect_type="sql_timing", severity=syntribos.MEDIUM, confidence=syntribos.MEDIUM, - description=("A response to one of our payload requests has " - "taken too long compared to the baseline " - "request. This could indicate a vulnerability " - "to time-based SQL injection attacks")) + description=(_("A response to one of our payload requests has " + "taken too long compared to the baseline " + "request. This could indicate a vulnerability " + "to time-based SQL injection attacks"))) class SQLInjectionParams(SQLInjectionBody): diff --git a/syntribos/tests/fuzz/user_defined.py b/syntribos/tests/fuzz/user_defined.py index e107b057..ed5d43dd 100644 --- a/syntribos/tests/fuzz/user_defined.py +++ b/syntribos/tests/fuzz/user_defined.py @@ -16,6 +16,7 @@ import os from oslo_config import cfg import syntribos +from syntribos._i18n import _ from syntribos.checks import has_string as has_string from syntribos.checks import time_diff as time_diff from syntribos.tests.fuzz import base_fuzz @@ -66,11 +67,11 @@ class UserDefinedVulnBody(base_fuzz.BaseFuzzTestCase): defect_type="user_defined_string_timing", severity=syntribos.MEDIUM, confidence=syntribos.MEDIUM, - description=("A response to one of the payload requests has " - "taken too long compared to the baseline " - "request. This could indicate a vulnerability " - "to time-based injection attacks using the user " - "provided strings.")) + description=(_("A response to one of the payload requests has " + "taken too long compared to the baseline " + "request. This could indicate a vulnerability " + "to time-based injection attacks using the user" + " provided strings."))) @classmethod def get_test_cases(cls, filename, file_content): diff --git a/syntribos/tests/headers/cors.py b/syntribos/tests/headers/cors.py index 7764d9da..934983f9 100644 --- a/syntribos/tests/headers/cors.py +++ b/syntribos/tests/headers/cors.py @@ -15,6 +15,7 @@ from oslo_config import cfg import syntribos +from syntribos._i18n import _ from syntribos.checks.header import cors from syntribos.clients.http import client from syntribos.clients.http import parser @@ -59,6 +60,6 @@ class CorsHeader(base.BaseTestCase): severity=test_severity, confidence=syntribos.HIGH, description=( - "CORS header vulnerability found.\n" - "Make sure that the header is not assigned " - "a wildcard character.")) + _("CORS header vulnerability found.\n" + "Make sure that the header is not assigned " + "a wildcard character."))) diff --git a/syntribos/tests/headers/xst.py b/syntribos/tests/headers/xst.py index d1337e54..154afc42 100644 --- a/syntribos/tests/headers/xst.py +++ b/syntribos/tests/headers/xst.py @@ -15,6 +15,7 @@ from oslo_config import cfg import syntribos +from syntribos._i18n import _ from syntribos.checks.header import xst from syntribos.clients.http import client from syntribos.clients.http import parser @@ -61,13 +62,14 @@ class XstHeader(base.BaseTestCase): xst_slugs = [ slugs for slugs in self.test_signals.all_slugs if "HEADER_XST" in slugs] - for _ in xst_slugs: + for i in xst_slugs: # noqa test_severity = syntribos.LOW self.register_issue( defect_type="XST_HEADER", severity=test_severity, confidence=syntribos.HIGH, - description=( - "XST vulnerability found.\n" - "Make sure that response to a TRACE request is filtered." - )) + description=(_("XST vulnerability found.\n" + "Make sure that response to a " + "TRACE request is filtered." + ) + )) diff --git a/syntribos/tests/transport_layer/ssl.py b/syntribos/tests/transport_layer/ssl.py index 93db3674..354e77d0 100644 --- a/syntribos/tests/transport_layer/ssl.py +++ b/syntribos/tests/transport_layer/ssl.py @@ -12,6 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. import syntribos +from syntribos._i18n import _ from syntribos.checks import https_check from syntribos.tests import base @@ -28,8 +29,8 @@ class SSLTestCase(base.BaseTestCase): if "HTTP_LINKS_PRESENT" in self.init_signals: self.register_issue( - defect_type="SSL_ERROR", + defect_type=_("SSL_ERROR"), severity=syntribos.MEDIUM, confidence=syntribos.HIGH, - description=("Make sure that all the returned endpoint URIs" - " use 'https://' and not 'http://'")) + description=(_("Make sure that all the returned endpoint URIs" + " use 'https://' and not 'http://'"))) diff --git a/syntribos/utils/cleanup.py b/syntribos/utils/cleanup.py index 22fc9872..de190e68 100644 --- a/syntribos/utils/cleanup.py +++ b/syntribos/utils/cleanup.py @@ -20,7 +20,7 @@ def delete_temps(): """Deletes all temporary dirs used for saving cached files.""" remote_dirs = set(syntribos.utils.remotes.remote_dirs) temp_dirs = set(syntribos.utils.remotes.temp_dirs) - [delete_dir(temp_dir) for temp_dir in temp_dirs] # noqa + [delete_dir(temp_dir) for temp_dir in temp_dirs] # noqa if remote_dirs - temp_dirs: print("All downloaded files have been saved to: {}".format( ",".join([ele for ele in (remote_dirs - temp_dirs)]))) diff --git a/syntribos/utils/env.py b/syntribos/utils/env.py index 3d41c086..6ed4390c 100644 --- a/syntribos/utils/env.py +++ b/syntribos/utils/env.py @@ -23,6 +23,7 @@ import requests from six.moves import input import syntribos +from syntribos._i18n import _, _LE, _LW # noqa from syntribos.utils import remotes FOLDER = ".syntribos" @@ -48,7 +49,7 @@ def get_user_home_root(): except OSError as e: # Refer https://mail.python.org/pipermail/python-bugs-list/ # 2002-July/012691.html - LOG.error("Exception thrown in : %s", e) + LOG.error(_LE("Exception thrown in : %s") % e) user = pwd.getpwuid(os.getuid())[0] home_path = "~{0}/{1}".format(user, FOLDER) return expand_path(home_path) @@ -111,15 +112,17 @@ def safe_makedirs(path, force=False): try: os.makedirs(path) except (OSError, IOError): - LOG.exception("Error creating folder (%s).", path) + LOG.exception(_("Error creating folder (%s).") % path) elif os.path.exists(path) and force: try: shutil.rmtree(path) os.makedirs(path) except (OSError, IOError): - LOG.exception("Error overwriting existing folder (%s).", path) + LOG.exception( + _("Error overwriting existing folder (%s).") % path) else: - LOG.warning("Folder was already found (%s). Skipping.", path) + LOG.warning( + _LW("Folder was already found (%s). Skipping.") % path) def create_env_dirs(root_dir, force=False): @@ -240,32 +243,37 @@ def initialize_syntribos_env(): payloads_dir = folders_created[1] if not CONF.sub_command.no_downloads: - print("\nDownloading payload files to {0}...".format(payloads_dir)) + print( + _("\nDownloading payload files to %s...") % payloads_dir) try: remote_path = remotes.get(CONF.remote.payloads_uri, payloads_dir) conf_file = create_conf_file(folders_created, remote_path) - print("Download successful!") + print(_("Download successful!")) except (requests.ConnectionError, IOError): - print("Download failed. If you would still like to download " - "payload files, please consult our documentation about the" - "'syntribos download' command or do so manually.") + print(_("Download failed. If you would still like to download" + " payload files, please consult our documentation" + " about the 'syntribos download' command or do so" + " manually.")) conf_file = create_conf_file(folders_created) else: conf_file = create_conf_file(folders_created) logging.disable(logging.NOTSET) - print("\nSyntribos has been initialized!") - print("Folders created:\n\t{0}".format("\n\t".join(folders_created))) - print("Configuration file:\n\t{0}".format(conf_file)) - print("\nYou'll need to edit your configuration file to specify the " - "endpoint to test and any other configuration options you want.") - print("\nBy default, syntribos does not ship with any template files, " - "which are required for syntribos to run. However, we provide a\n " - "'syntribos download' command to fetch template files remotely. " - "Please see our documentation for this subcommand, or run\n " - "'syntribos download --templates' to download our default set of " - "OpenStack templates.") + print(_("\nSyntribos has been initialized!")) + print( + _("Folders created:\n\t%s") % "\n\t".join(folders_created)) + print(_("Configuration file:\n\t%s") % conf_file) + print(_( + "\nYou'll need to edit your configuration file to specify the " + "endpoint to test and any other configuration options you want.")) + print(_( + "\nBy default, syntribos does not ship with any template files, " + "which are required for syntribos to run. However, we provide a\n " + "'syntribos download' command to fetch template files remotely. " + "Please see our documentation for this subcommand, or run\n " + "'syntribos download --templates' to download our default set of " + "OpenStack templates.")) print(syntribos.SEP) @@ -302,29 +310,37 @@ def download_wrapper(): os.path.join(get_syntribos_root(), "payloads")) if not CONF.sub_command.templates and not CONF.sub_command.payloads: - print("Please specify the --templates flag and/or the --payloads flag " - "to this command.\nNo files have been downloaded.\n") + print( + _( + "Please specify the --templates flag and/or the --payloads" + "flag to this command.\nNo files have been downloaded.\n")) if CONF.sub_command.templates: - print("Downloading template files from {0} to {1}...".format( - templates_uri, templates_dir)) + print(_( + "Downloading template files from %(uri)s to %(dir)s..." + ) % {"uri": templates_uri, "dir": templates_dir}) try: remotes.get(templates_uri, templates_dir) - print("Download successful! To use these templates, edit your " - "config file to update the location of templates.") + print(_( + "Download successful! To use these templates, edit your " + "config file to update the location of templates.")) except Exception: - print("Template download failed. Our documentation contains " - "instructions to provide templates manually.") + print(_( + "Template download failed. Our documentation contains " + "instructions to provide templates manually.")) exit(1) if CONF.sub_command.payloads: - print("Downloading payload files from {0} to {1}...\n".format( - payloads_uri, payloads_dir)) + print(_( + "Downloading payload files from %(uri)s to %(dir)s...\n") % { + "uri": payloads_uri, "dir": payloads_dir}) try: remotes.get(payloads_uri, payloads_dir) - print("Download successful! To use these payloads, edit your " - "config file to update the location of payloads.") + print(_( + "Download successful! To use these payloads, edit your " + "config file to update the location of payloads.")) except Exception: - print("Payload download failed. Our documentation contains " - "instructions to provide payloads manually.") + print(_( + "Payload download failed. Our documentation contains " + "instructions to provide payloads manually.")) exit(1) diff --git a/syntribos/utils/remotes.py b/syntribos/utils/remotes.py index 84137d92..c91919c7 100644 --- a/syntribos/utils/remotes.py +++ b/syntribos/utils/remotes.py @@ -20,6 +20,7 @@ import tempfile from oslo_config import cfg from syntribos.clients.http.client import SynHTTPClient +from syntribos._i18n import _LI, _LE, _LW # noqa CONF = cfg.CONF LOG = logging.getLogger(__name__) @@ -60,8 +61,7 @@ def download(uri, cache_dir=None): cache_dir = tempfile.mkdtemp() temp_dirs.append(cache_dir) remote_dirs.append(cache_dir) - log_string = "Remote file location: {}".format(remote_dirs) - LOG.debug(log_string) + LOG.debug(_LI("Remote file location: %s") % remote_dirs) resp, _ = SynHTTPClient().request("GET", uri) os.chdir(cache_dir) saved_umask = os.umask(0o77) @@ -71,7 +71,7 @@ def download(uri, cache_dir=None): fh.write(resp.content) return os.path.abspath(fname) except IOError: - LOG.error("IOError in writing the downloaded file to disk.") + LOG.error(_LE("IOError in writing the downloaded file to disk.")) finally: os.umask(saved_umask) @@ -100,7 +100,8 @@ def extract_tar(abs_path): try: os.mkdir("remote") except OSError: - LOG.debug("path exists already, not creating remote directory.") + LOG.error(_LE( + "path exists already, not creating remote directory.")) remote_path = os.path.abspath("remote") def safe_paths(tar_meta): @@ -137,14 +138,14 @@ def get(uri, cache_dir=None): temp = tempfile.TemporaryFile(dir=os.path.abspath(user_base_dir)) temp.close() except OSError: - LOG.error("Failed to write remote files to: %s", + LOG.error(_("Failed to write remote files to: %s") % os.path.abspath(user_base_dir)) exit(1) abs_path = download(uri, os.path.abspath(user_base_dir)) else: abs_path = download(uri) if not file_type(abs_path) == "gz": - msg = "Not a gz file, returning abs_path" + msg = _("Not a gz file, returning abs_path") LOG.debug(msg) return abs_path return extract_tar(abs_path)