diff --git a/.travis.yml b/.travis.yml index a1ecfc9d5..c38344b42 100644 --- a/.travis.yml +++ b/.travis.yml @@ -32,10 +32,9 @@ matrix: python: 3.6 - env: TOXENV=py37 python: 3.7 - dist: xenial # Required for Python >= 3.7 (travis-ci/travis-ci#9069) - - env: TOXENV=py38 - python: 3.8 - dist: xenial # Required for Python >= 3.7 (travis-ci/travis-ci#9069) + dist: xenial # required for Python >= 3.7 (travis-ci/travis-ci#9069) + # - env: TOXENV=pypy + # python: pypy install: - pip install tox script: make test && docker build -t $DOCKER_IMAGE:$DOCKER_IMAGE_TAG --no-cache . diff --git a/Dockerfile b/Dockerfile index e5fe76289..c2f7f2c9b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,6 +4,8 @@ RUN apk add --no-cache jq git curl bash openssl RUN mkdir -p /code COPY . /usr/src/app WORKDIR /usr/src/app +RUN apk add --no-cache --virtual .build-deps gcc musl-dev +RUN pip install cython RUN easy_install /usr/src/app WORKDIR /code ENTRYPOINT [ "/usr/src/app/run-scan.sh" ] diff --git a/Makefile b/Makefile index 74049f23c..db55d9424 100644 --- a/Makefile +++ b/Makefile @@ -1,3 +1,5 @@ +PROJECT_DIR := $(shell pwd) + .PHONY: minimal minimal: setup @@ -22,3 +24,13 @@ clean: super-clean: clean rm -rf .tox rm -rf venv + +.PHONY: fix-db2-mac +fix-db2-mac: + # comment out lines for any interpreters that aren't installed on your machine + install_name_tool -change libdb2.dylib $(PROJECT_DIR)/.tox/py27/lib/python2.7/site-packages/clidriver/lib/libdb2.dylib $(PROJECT_DIR)/.tox/py27/lib/python2.7/site-packages/ibm_db.so + install_name_tool -change libbd2.dylib $(PROJECT_DIR)/.tox/py35/lib/python3.5/site-packages/clidriver/lib/libdb2.dylib $(PROJECT_DIR)/.tox/py35/lib/python3.5/site-packages/ibm_db.cpython-35m-darwin.so + install_name_tool -change libbd2.dylib $(PROJECT_DIR)/.tox/py36/lib/python3.6/site-packages/clidriver/lib/libdb2.dylib $(PROJECT_DIR)/.tox/py36/lib/python3.6/site-packages/ibm_db.cpython-36m-darwin.so + install_name_tool -change libdb2.dylib $(PROJECT_DIR)/.tox/py37/lib/python3.7/site-packages/clidriver/lib/libdb2.dylib $(PROJECT_DIR)/.tox/py37/lib/python3.7/site-packages/ibm_db.cpython-37m-darwin.so + install_name_tool -change libdb2.dylib $(PROJECT_DIR)/.tox/pypy/site-packages/clidriver/lib/libdb2.dylib $(PROJECT_DIR)/.tox/pypy/site-packages/ibm_db.pypy-41.so + install_name_tool -change libdb2.dylib $(PROJECT_DIR)/.tox/pypy3/site-packages/clidriver/lib/libdb2.dylib $(PROJECT_DIR)/.tox/pypy3/site-packages/ibm_db.pypy3-71-darwin.so diff --git a/detect_secrets/core/usage.py b/detect_secrets/core/usage.py index 8bbaff7d2..c5d26bbad 100644 --- a/detect_secrets/core/usage.py +++ b/detect_secrets/core/usage.py @@ -498,6 +498,12 @@ class PluginOptions: disable_help_text='Disable scanning for SoftLayer keys', is_default=True, ), + PluginDescriptor( + classname='DB2Detector', + disable_flag_text='--no-db2-scan', + disable_help_text='Disable scanning for DB2 credentials', + is_default=True, + ), ] default_plugins_list = [ diff --git a/detect_secrets/plugins/common/initialize.py b/detect_secrets/plugins/common/initialize.py index a22f2ce95..bf011bfbf 100644 --- a/detect_secrets/plugins/common/initialize.py +++ b/detect_secrets/plugins/common/initialize.py @@ -4,6 +4,7 @@ from ..base import BasePlugin from ..basic_auth import BasicAuthDetector # noqa: F401 from ..common.util import get_mapping_from_secret_type_to_class_name +from ..db2 import DB2Detector # noqa: F401 from ..gh import GHDetector # noqa: F401 from ..high_entropy_strings import Base64HighEntropyString # noqa: F401 from ..high_entropy_strings import HexHighEntropyString # noqa: F401 diff --git a/detect_secrets/plugins/common/util.py b/detect_secrets/plugins/common/util.py index 321092533..267e17dee 100644 --- a/detect_secrets/plugins/common/util.py +++ b/detect_secrets/plugins/common/util.py @@ -1,11 +1,21 @@ -import importlib.util -import inspect -import os -from abc import abstractproperty -from functools import lru_cache - -from detect_secrets.plugins.base import BasePlugin -from detect_secrets.util import get_root_directory +try: + from functools import lru_cache +except ImportError: # pragma: no cover + from functools32 import lru_cache + +# These plugins need to be imported here so that globals() +# can find them. +from ..artifactory import ArtifactoryDetector # noqa: F401 +from ..aws import AWSKeyDetector # noqa: F401 +from ..base import BasePlugin +from ..basic_auth import BasicAuthDetector # noqa: F401 +from ..db2 import DB2Detector # noqa: F401 +from ..high_entropy_strings import Base64HighEntropyString # noqa: F401 +from ..high_entropy_strings import HexHighEntropyString # noqa: F401 +from ..keyword import KeywordDetector # noqa: F401 +from ..private_key import PrivateKeyDetector # noqa: F401 +from ..slack import SlackDetector # noqa: F401 +from ..stripe import StripeDetector # noqa: F401 @lru_cache(maxsize=1) diff --git a/detect_secrets/plugins/db2.py b/detect_secrets/plugins/db2.py new file mode 100644 index 000000000..33c4b10e0 --- /dev/null +++ b/detect_secrets/plugins/db2.py @@ -0,0 +1,185 @@ +from __future__ import absolute_import + +import re + +import ibm_db + +from .base import RegexBasedDetector +from detect_secrets.core.constants import VerifiedResult + + +class DB2Detector(RegexBasedDetector): + + secret_type = 'DB2 Credentials' + + begin = r'(?:(?<=\W)|(?<=^))' + opt_quote = r'(?:"|\'|)' + opt_db = r'(?:db2|dashdb|db|)' + opt_dash_undrscr = r'(?:_|-|)' + password_keyword = r'(?:password|pwd|pass|passwd)' + opt_space = r'(?: *)' + assignment = r'(?:=|:|:=|=>|::)' + # catch any character except newline and quotations, we exclude these + # because the regex will erronously match them when present at the end of the password + # db2 password requirements vary by version so we cast a broad net + password = r'([^\n"\']+)' + denylist = ( + re.compile( + r'{begin}{opt_quote}{opt_db}{opt_dash_undrscr}{password_keyword}{opt_quote}{opt_space}' + '{assignment}{opt_space}{opt_quote}{password}{opt_quote}'.format( + begin=begin, + opt_quote=opt_quote, + opt_db=opt_db, + opt_dash_undrscr=opt_dash_undrscr, + password_keyword=password_keyword, + opt_space=opt_space, + assignment=assignment, + password=password, + ), flags=re.IGNORECASE, + ), + ) + + username_keyword_regex = r'(?:user|user(?:_|-|)name|uid|user(?:_|-|)id|u(?:_|-|)name)' + username_regex = r'([a-zA-Z0-9_]+)' + + database_keyword_regex = r'(?:database|db|database(?:_|-|)name|db(?:_|-|)name)' + database_regex = r'([a-zA-Z0-9_-]+)' + + port_keyword_regex = r'(?:port|port(?:_|-|)number)' + port_regex = r'([0-9]{1,5})' + + hostname_keyword_regex = ( + r'(?:host|host(?:_|-|)name|host(?:_|-|)address|' + r'host(?:_|-|)ip|host(?:_|-|)ip(?:_|-|)address)' + ) + hostname_regex = ( + r'((?:(?:[a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)' + r'*(?:.\[A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9]))' + ) + + def verify(self, token, content, potential_secret, timeout=5): + + username_matches = find_other_factor( + content, self.username_keyword_regex, + self.username_regex, + ) + if not username_matches: + return VerifiedResult.UNVERIFIED + + database_matches = find_other_factor( + content, self.database_keyword_regex, + self.database_regex, + ) + port_matches = find_other_factor( + content, self.port_keyword_regex, + self.port_regex, + ) + hostname_matches = find_other_factor( + content, self.hostname_keyword_regex, + self.hostname_regex, + ) + + url_matches = get_hostname_port_database_from_url( + content, self.hostname_regex, self.port_regex, self.database_regex, + ) + for match in url_matches: + hostname, port, database = match + hostname_matches.append(hostname) + port_matches.append(port) + database_matches.append(database) + + if not database_matches or not port_matches or not hostname_matches: + return VerifiedResult.UNVERIFIED + + for username in username_matches: # pragma: no cover + for database in database_matches: # pragma: no cover + for port in port_matches: # pragma: no cover + for hostname in hostname_matches: # pragma: no cover + verify_result = verify_db2_credentials( + database, hostname, port, username, token, timeout, + ) + if verify_result == VerifiedResult.VERIFIED_TRUE: + potential_secret.other_factors['database'] = database + potential_secret.other_factors['hostname'] = hostname + potential_secret.other_factors['port'] = port + potential_secret.other_factors['username'] = username + return verify_result + + return VerifiedResult.VERIFIED_FALSE + + +def verify_db2_credentials( + database, hostname, port, username, password, timeout=5, +): # pragma: no cover + try: + conn_str = 'database={database};hostname={hostname};port={port};' + \ + 'protocol=tcpip;uid={username};pwd={password};' + \ + 'ConnectTimeout={timeout}' + conn_str = conn_str.format( + database=database, + hostname=hostname, + port=port, + username=username, + password=password, + timeout=timeout, + ) + ibm_db_conn = ibm_db.connect(conn_str, '', '') + if ibm_db_conn: + return VerifiedResult.VERIFIED_TRUE + else: + return VerifiedResult.VERIFIED_FALSE + except Exception as e: + if 'Timeout' in str(e): + return VerifiedResult.UNVERIFIED + else: + return VerifiedResult.VERIFIED_FALSE + + +def find_other_factor(content, factor_keyword_regex, factor_regex): + begin = r'(?:(?<=\W)|(?<=^))' + opt_quote = r'(?:"|\'|)' + opt_db = r'(?:db2|dashdb|db|)' + opt_dash_undrscr = r'(?:_|-|)' + opt_space = r'(?: *)' + assignment = r'(?:=|:|:=|=>|::)' + regex = re.compile( + r'{begin}{opt_quote}{opt_db}{opt_dash_undrscr}{factor_keyword}{opt_quote}{opt_space}' + '{assignment}{opt_space}{opt_quote}{factor}{opt_quote}'.format( + begin=begin, + opt_quote=opt_quote, + opt_db=opt_db, + opt_dash_undrscr=opt_dash_undrscr, + factor_keyword=factor_keyword_regex, + opt_space=opt_space, + assignment=assignment, + factor=factor_regex, + ), flags=re.IGNORECASE, + ) + + return [ + match + for line in content.splitlines() + for match in regex.findall(line) + ] + + +def get_hostname_port_database_from_url(content, hostname_regex, port_regex, database_regex): + """ + Gets hostname, port, and database factors from a jdbc db2 url + Accepts: content to scan, regexes to capture hostname, port, and database + Returns: list of tuples of format (hostname, port, database), + or empty list if no matches + """ + regex = re.compile( + r'jdbc:db2:\/\/{hostname}:{port}\/{database}'.format( + hostname=hostname_regex, + port=port_regex, + database=database_regex, + ), + ) + + return [ + (match[0], match[1], match[2]) + for line in content.splitlines() + for match in regex.findall(line) + ] diff --git a/detect_secrets/plugins/gh.py b/detect_secrets/plugins/gh.py index 76e242dd4..83c4b7dd9 100644 --- a/detect_secrets/plugins/gh.py +++ b/detect_secrets/plugins/gh.py @@ -13,7 +13,7 @@ class GHDetector(RegexBasedDetector): secret_type = 'GitHub Credentials' opt_github = r'(?:github|gh|ghe|git|)' - opt_space = r'(?: |)' + opt_space = r'(?: *)' opt_quote = r'(?:"|\'|)' opt_assignment = r'(?:=|:|:=|=>|)' opt_dash_undrscr = r'(?:_|-|)' diff --git a/detect_secrets/plugins/softlayer.py b/detect_secrets/plugins/softlayer.py index a05f03d85..7ad75773b 100644 --- a/detect_secrets/plugins/softlayer.py +++ b/detect_secrets/plugins/softlayer.py @@ -16,7 +16,7 @@ class SoftLayerDetector(RegexBasedDetector): opt_dash_undrscr = r'(?:_|-|)' opt_api = r'(?:api|)' key_or_pass = r'(?:key|pwd|password|pass|token)' - opt_space = r'(?: |)' + opt_space = r'(?: *)' opt_assignment = r'(?:=|:|:=|=>|)' secret = r'([a-z0-9]{64})' denylist = [ diff --git a/requirements-dev.txt b/requirements-dev.txt index 442ab5ac6..126d379b4 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -12,3 +12,4 @@ tox-pip-extensions tox>=3.8 unidiff responses +ibm_db diff --git a/setup.py b/setup.py index 53fd95a44..2d741a92d 100644 --- a/setup.py +++ b/setup.py @@ -18,6 +18,7 @@ install_requires=[ 'pyyaml', 'requests', + 'ibm_db', ], extras_require={ 'word_list': [ diff --git a/tests/core/usage_test.py b/tests/core/usage_test.py index 65b282fa3..0bc4702c7 100644 --- a/tests/core/usage_test.py +++ b/tests/core/usage_test.py @@ -36,6 +36,7 @@ def test_consolidates_output_basic(self): 'ArtifactoryDetector': {}, 'GHDetector': {}, 'SoftLayerDetector': {}, + 'DB2Detector': {}, } def test_consolidates_removes_disabled_plugins(self): diff --git a/tests/main_test.py b/tests/main_test.py index 8497a0b13..3a636abf2 100644 --- a/tests/main_test.py +++ b/tests/main_test.py @@ -211,6 +211,7 @@ def test_scan_string_basic_default( AWSKeyDetector : False ArtifactoryDetector: False BasicAuthDetector : False + DB2Detector : False GHDetector : False PrivateKeyDetector : False SlackDetector : False @@ -355,6 +356,9 @@ def test_old_baseline_ignored_with_update_flag( { 'name': 'BasicAuthDetector', }, + { + 'name': 'DB2Detector', + }, { 'name': 'GHDetector', }, @@ -398,6 +402,9 @@ def test_old_baseline_ignored_with_update_flag( { 'name': 'BasicAuthDetector', }, + { + 'name': 'DB2Detector', + }, { 'name': 'GHDetector', }, @@ -498,6 +505,9 @@ def test_old_baseline_ignored_with_update_flag( { 'name': 'BasicAuthDetector', }, + { + 'name': 'DB2Detector', + }, { 'name': 'GHDetector', }, @@ -540,6 +550,9 @@ def test_old_baseline_ignored_with_update_flag( { 'name': 'BasicAuthDetector', }, + { + 'name': 'DB2Detector', + }, { 'name': 'GHDetector', }, @@ -681,6 +694,9 @@ def test_scan_with_default_plugin(self): { 'name': 'BasicAuthDetector', }, + { + 'name': 'DB2Detector', + }, { 'name': 'GHDetector', }, diff --git a/tests/plugins/db2_test.py b/tests/plugins/db2_test.py new file mode 100644 index 000000000..d4cec6f4e --- /dev/null +++ b/tests/plugins/db2_test.py @@ -0,0 +1,299 @@ +from __future__ import absolute_import + +import textwrap + +import pytest +from mock import MagicMock +from mock import patch + +from detect_secrets.core.constants import VerifiedResult +from detect_secrets.core.potential_secret import PotentialSecret +from detect_secrets.plugins.db2 import DB2Detector +from detect_secrets.plugins.db2 import find_other_factor +from detect_secrets.plugins.db2 import get_hostname_port_database_from_url + + +DB2_USER = 'fake_user' +DB2_PASSWORD = 'fake_password' +DB2_PORT = '1234' +DB2_HOSTNAME = 'fake.host.name' +DB2_DATABASE = 'fake_database' +DB2_CONN_STRING = 'database={DB2_DATABASE};hostname={DB2_HOSTNAME};port={DB2_PORT};' + \ + 'protocol=tcpip;uid={DB2_USER};pwd={DB2_PASSWORD};ConnectTimeout=5' +DB2_CONN_STRING = DB2_CONN_STRING.format( + DB2_DATABASE=DB2_DATABASE, + DB2_HOSTNAME=DB2_HOSTNAME, + DB2_PORT=DB2_PORT, + DB2_USER=DB2_USER, + DB2_PASSWORD=DB2_PASSWORD, +) + + +class TestGHDetector(object): + + @pytest.mark.parametrize( + 'token, payload, should_flag', + [ + ( + 'secret', + 'database=test;hostname=host.test.com;' + 'port=1;protocol=tcpip;uid=testid;pwd=secret', True, + ), + ('$omespeci@!ch@r$', 'dbpwd=$omespeci@!ch@r$', True), + ('astring', 'db2_password = "astring"', True), + ('Iusedb2!', '"password": "Iusedb2!"', True), + ('ilikespaces', 'password = "ilikespaces"', True), + (':anothersyntax!', 'pwd::anothersyntax!', True), + ('@#!%#', 'DB2_PASSWORD = "@#!%#"', True), + ('pass', 'dashdb-password = "pass"', True), + ('', 'dashdb_host = notapassword', False), + ('', 'someotherpassword = "doesnt start right"', False), + ], + ) + def test_analyze_string(self, token, payload, should_flag): + logic = DB2Detector() + + output = logic.analyze_string(payload, 1, 'mock_filename') + assert len(output) == int(should_flag) + if len(output) > 0: + assert list(output.keys())[0].secret == token + + @patch('detect_secrets.plugins.db2.ibm_db.connect') + def test_verify_invalid_connect_returns_none(self, mock_db2_connect): + mock_db2_connect.return_value = None + + potential_secret = PotentialSecret('test db2', 'test filename', DB2_PASSWORD) + assert DB2Detector().verify( + DB2_PASSWORD, + '''user={}, + password={}, + database={}, + host={}, + port={}'''.format(DB2_USER, DB2_PASSWORD, DB2_DATABASE, DB2_HOSTNAME, DB2_PORT), + potential_secret, + ) == VerifiedResult.VERIFIED_FALSE + + mock_db2_connect.assert_called_with(DB2_CONN_STRING, '', '') + + @patch('detect_secrets.plugins.db2.ibm_db.connect') + def test_verify_invalid_connect_throws_exception(self, mock_db2_connect): + mock_db2_connect.side_effect = Exception('oops') + + potential_secret = PotentialSecret('test db2', 'test filename', DB2_PASSWORD) + assert DB2Detector().verify( + DB2_PASSWORD, + '''user={}, + password={}, + database={}, + host={}, + port={}'''.format(DB2_USER, DB2_PASSWORD, DB2_DATABASE, DB2_HOSTNAME, DB2_PORT), + potential_secret, + ) == VerifiedResult.VERIFIED_FALSE + + mock_db2_connect.assert_called_with(DB2_CONN_STRING, '', '') + + @patch('detect_secrets.plugins.db2.ibm_db.connect') + def test_verify_valid_secret(self, mock_db2_connect): + mock_db2_connect.return_value = MagicMock() + + potential_secret = PotentialSecret('test db2', 'test filename', DB2_PASSWORD) + assert DB2Detector().verify( + DB2_PASSWORD, + '''user={}, + password={}, + database={}, + host={}, + port={}'''.format(DB2_USER, DB2_PASSWORD, DB2_DATABASE, DB2_HOSTNAME, DB2_PORT), + potential_secret, + ) == VerifiedResult.VERIFIED_TRUE + + mock_db2_connect.assert_called_with(DB2_CONN_STRING, '', '') + assert potential_secret.other_factors['database'] == DB2_DATABASE + assert potential_secret.other_factors['hostname'] == DB2_HOSTNAME + assert potential_secret.other_factors['port'] == DB2_PORT + assert potential_secret.other_factors['username'] == DB2_USER + + @patch('detect_secrets.plugins.db2.ibm_db.connect') + def test_verify_valid_secret_in_single_quotes(self, mock_db2_connect): + mock_db2_connect.return_value = MagicMock() + + potential_secret = PotentialSecret('test db2', 'test filename', DB2_PASSWORD) + assert DB2Detector().verify( + DB2_PASSWORD, + '''user='{}', + password='{}', + database='{}', + host='{}', + port='{}' + '''.format(DB2_USER, DB2_PASSWORD, DB2_DATABASE, DB2_HOSTNAME, DB2_PORT), + potential_secret, + ) == VerifiedResult.VERIFIED_TRUE + + mock_db2_connect.assert_called_with(DB2_CONN_STRING, '', '') + assert potential_secret.other_factors['database'] == DB2_DATABASE + assert potential_secret.other_factors['hostname'] == DB2_HOSTNAME + assert potential_secret.other_factors['port'] == DB2_PORT + assert potential_secret.other_factors['username'] == DB2_USER + + @patch('detect_secrets.plugins.db2.ibm_db.connect') + def test_verify_valid_secret_in_double_quotes(self, mock_db2_connect): + mock_db2_connect.return_value = MagicMock() + + potential_secret = PotentialSecret('test db2', 'test filename', DB2_PASSWORD) + assert DB2Detector().verify( + DB2_PASSWORD, + '''user="{}", + password="{}", + database="{}", + host="{}", + port="{}" + '''.format(DB2_USER, DB2_PASSWORD, DB2_DATABASE, DB2_HOSTNAME, DB2_PORT), + potential_secret, + ) == VerifiedResult.VERIFIED_TRUE + + mock_db2_connect.assert_called_with(DB2_CONN_STRING, '', '') + assert potential_secret.other_factors['database'] == DB2_DATABASE + assert potential_secret.other_factors['hostname'] == DB2_HOSTNAME + assert potential_secret.other_factors['port'] == DB2_PORT + assert potential_secret.other_factors['username'] == DB2_USER + + @patch('detect_secrets.plugins.db2.ibm_db.connect') + def test_verify_from_url(self, mock_db2_connect): + mock_db2_connect.return_value = MagicMock() + + potential_secret = PotentialSecret('test db2', 'test filename', DB2_PASSWORD) + assert DB2Detector().verify( + DB2_PASSWORD, + '''user={}, + password={}, + url=jdbc:db2://{}:{}/{}, + '''.format(DB2_USER, DB2_PASSWORD, DB2_HOSTNAME, DB2_PORT, DB2_DATABASE), + potential_secret, + ) == VerifiedResult.VERIFIED_TRUE + + mock_db2_connect.assert_called_with(DB2_CONN_STRING, '', '') + assert potential_secret.other_factors['database'] == DB2_DATABASE + assert potential_secret.other_factors['hostname'] == DB2_HOSTNAME + assert potential_secret.other_factors['port'] == DB2_PORT + assert potential_secret.other_factors['username'] == DB2_USER + + @patch('detect_secrets.plugins.db2.ibm_db.connect') + def test_verify_times_out(self, mock_db2_connect): + mock_db2_connect.side_effect = Exception('Timeout') + + potential_secret = PotentialSecret('test db2', 'test filename', DB2_PASSWORD) + assert DB2Detector().verify( + DB2_PASSWORD, + '''user={}, + password={}, + database={}, + host={}, + port={}'''.format(DB2_USER, DB2_PASSWORD, DB2_DATABASE, DB2_HOSTNAME, DB2_PORT), + potential_secret, + ) == VerifiedResult.UNVERIFIED + + mock_db2_connect.assert_called_with(DB2_CONN_STRING, '', '') + + def test_verify_no_other_factors(self): + potential_secret = PotentialSecret('test db2', 'test filename', DB2_PASSWORD) + assert DB2Detector().verify( + DB2_PASSWORD, + 'password={}'.format(DB2_PASSWORD), + potential_secret, + ) == VerifiedResult.UNVERIFIED + + +@pytest.mark.parametrize( + 'content, factor_keyword_regex, factor_regex, expected_output', + ( + ( + textwrap.dedent(""" + user = {} + """)[1:-1].format( + DB2_USER, + ), + DB2Detector().username_keyword_regex, + DB2Detector().username_regex, + [DB2_USER], + ), + ( + textwrap.dedent(""" + port = {} + """)[1:-1].format( + DB2_PORT, + ), + DB2Detector().port_keyword_regex, + DB2Detector().port_regex, + [DB2_PORT], + ), + ( + textwrap.dedent(""" + database = {} + """)[1:-1].format( + DB2_DATABASE, + ), + DB2Detector().database_keyword_regex, + DB2Detector().database_regex, + [DB2_DATABASE], + ), + ( + textwrap.dedent(""" + host = {} + """)[1:-1].format( + DB2_HOSTNAME, + ), + DB2Detector().hostname_keyword_regex, + DB2Detector().hostname_regex, + [DB2_HOSTNAME], + ), + ), +) +def test_find_other_factor(content, factor_keyword_regex, factor_regex, expected_output): + assert find_other_factor(content, factor_keyword_regex, factor_regex) == expected_output + + +@pytest.mark.parametrize( + 'content, hostname_regex, port_regex, database_regex, expected_output', + ( + ( + textwrap.dedent(""" + jdbc:db2://{}:{}/{} + """)[1:-1].format( + DB2_HOSTNAME, + DB2_PORT, + DB2_DATABASE, + ), + DB2Detector().hostname_regex, + DB2Detector().port_regex, + DB2Detector().database_regex, + [(DB2_HOSTNAME, DB2_PORT, DB2_DATABASE)], + ), + ( + textwrap.dedent(""" + jdbc:db2://{}:{}/ + """)[1:-1].format( + DB2_HOSTNAME, + DB2_PORT, + ), + DB2Detector().hostname_regex, + DB2Detector().port_regex, + DB2Detector().database_regex, + [], + ), + ( + textwrap.dedent(""" + nonsense + """), + DB2Detector().hostname_regex, + DB2Detector().port_regex, + DB2Detector().database_regex, + [], + ), + ), +) +def test_get_hostname_port_database_from_url( + content, hostname_regex, port_regex, database_regex, expected_output, +): + assert get_hostname_port_database_from_url( + content, hostname_regex, port_regex, database_regex, + ) == expected_output diff --git a/tests/pre_commit_hook_test.py b/tests/pre_commit_hook_test.py index 4af53d866..e97a7f1e2 100644 --- a/tests/pre_commit_hook_test.py +++ b/tests/pre_commit_hook_test.py @@ -244,6 +244,9 @@ def test_baseline_gets_updated( { 'name': 'BasicAuthDetector', }, + { + 'name': 'DB2Detector', + }, { 'name': 'GHDetector', },