Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit b88ad9e

Browse files
committedSep 9, 2020
DB2 Detector (Yelp#194)
Supports git-defenders/detect-secrets-discuss#190 DB2 Verification (Yelp#196) Supports git-defenders/detect-secrets-discuss#190 Use DB2 detector (Yelp#199) Supports git-defenders/detect-secrets-discuss#190 Refactor DB2 verification for calling externally (Yelp#203) Supports fixing bug [here](https://github.ibm.com/git-defenders/detect-secrets-stream/blob/master/detect_secrets_stream/validation/db2.py#L25) Catch DB2 hostname, port, database from connection url (Yelp#209) Supports git-defenders/detect-secrets-discuss#212 Timeout DB2 detector if it takes too long (Yelp#214)
1 parent 14ba284 commit b88ad9e

15 files changed

+550
-14
lines changed
 

‎.travis.yml

+3-4
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,9 @@ matrix:
3232
python: 3.6
3333
- env: TOXENV=py37
3434
python: 3.7
35-
dist: xenial # Required for Python >= 3.7 (travis-ci/travis-ci#9069)
36-
- env: TOXENV=py38
37-
python: 3.8
38-
dist: xenial # Required for Python >= 3.7 (travis-ci/travis-ci#9069)
35+
dist: xenial # required for Python >= 3.7 (travis-ci/travis-ci#9069)
36+
# - env: TOXENV=pypy
37+
# python: pypy
3938
install:
4039
- pip install tox
4140
script: make test && docker build -t $DOCKER_IMAGE:$DOCKER_IMAGE_TAG --no-cache .

‎Dockerfile

+2
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ RUN apk add --no-cache jq git curl bash openssl
44
RUN mkdir -p /code
55
COPY . /usr/src/app
66
WORKDIR /usr/src/app
7+
RUN apk add --no-cache --virtual .build-deps gcc musl-dev
8+
RUN pip install cython
79
RUN easy_install /usr/src/app
810
WORKDIR /code
911
ENTRYPOINT [ "/usr/src/app/run-scan.sh" ]

‎Makefile

+12
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
PROJECT_DIR := $(shell pwd)
2+
13
.PHONY: minimal
24
minimal: setup
35

@@ -22,3 +24,13 @@ clean:
2224
super-clean: clean
2325
rm -rf .tox
2426
rm -rf venv
27+
28+
.PHONY: fix-db2-mac
29+
fix-db2-mac:
30+
# comment out lines for any interpreters that aren't installed on your machine
31+
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
32+
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
33+
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
34+
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
35+
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
36+
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

‎detect_secrets/core/usage.py

+6
Original file line numberDiff line numberDiff line change
@@ -498,6 +498,12 @@ class PluginOptions:
498498
disable_help_text='Disable scanning for SoftLayer keys',
499499
is_default=True,
500500
),
501+
PluginDescriptor(
502+
classname='DB2Detector',
503+
disable_flag_text='--no-db2-scan',
504+
disable_help_text='Disable scanning for DB2 credentials',
505+
is_default=True,
506+
),
501507
]
502508

503509
default_plugins_list = [

‎detect_secrets/plugins/common/initialize.py

+1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from ..base import BasePlugin
55
from ..basic_auth import BasicAuthDetector # noqa: F401
66
from ..common.util import get_mapping_from_secret_type_to_class_name
7+
from ..db2 import DB2Detector # noqa: F401
78
from ..gh import GHDetector # noqa: F401
89
from ..high_entropy_strings import Base64HighEntropyString # noqa: F401
910
from ..high_entropy_strings import HexHighEntropyString # noqa: F401

‎detect_secrets/plugins/common/util.py

+18-8
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,21 @@
1-
import importlib.util
2-
import inspect
3-
import os
4-
from abc import abstractproperty
5-
from functools import lru_cache
6-
7-
from detect_secrets.plugins.base import BasePlugin
8-
from detect_secrets.util import get_root_directory
1+
try:
2+
from functools import lru_cache
3+
except ImportError: # pragma: no cover
4+
from functools32 import lru_cache
5+
6+
# These plugins need to be imported here so that globals()
7+
# can find them.
8+
from ..artifactory import ArtifactoryDetector # noqa: F401
9+
from ..aws import AWSKeyDetector # noqa: F401
10+
from ..base import BasePlugin
11+
from ..basic_auth import BasicAuthDetector # noqa: F401
12+
from ..db2 import DB2Detector # noqa: F401
13+
from ..high_entropy_strings import Base64HighEntropyString # noqa: F401
14+
from ..high_entropy_strings import HexHighEntropyString # noqa: F401
15+
from ..keyword import KeywordDetector # noqa: F401
16+
from ..private_key import PrivateKeyDetector # noqa: F401
17+
from ..slack import SlackDetector # noqa: F401
18+
from ..stripe import StripeDetector # noqa: F401
919

1020

1121
@lru_cache(maxsize=1)

‎detect_secrets/plugins/db2.py

+185
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
from __future__ import absolute_import
2+
3+
import re
4+
5+
import ibm_db
6+
7+
from .base import RegexBasedDetector
8+
from detect_secrets.core.constants import VerifiedResult
9+
10+
11+
class DB2Detector(RegexBasedDetector):
12+
13+
secret_type = 'DB2 Credentials'
14+
15+
begin = r'(?:(?<=\W)|(?<=^))'
16+
opt_quote = r'(?:"|\'|)'
17+
opt_db = r'(?:db2|dashdb|db|)'
18+
opt_dash_undrscr = r'(?:_|-|)'
19+
password_keyword = r'(?:password|pwd|pass|passwd)'
20+
opt_space = r'(?: *)'
21+
assignment = r'(?:=|:|:=|=>|::)'
22+
# catch any character except newline and quotations, we exclude these
23+
# because the regex will erronously match them when present at the end of the password
24+
# db2 password requirements vary by version so we cast a broad net
25+
password = r'([^\n"\']+)'
26+
denylist = (
27+
re.compile(
28+
r'{begin}{opt_quote}{opt_db}{opt_dash_undrscr}{password_keyword}{opt_quote}{opt_space}'
29+
'{assignment}{opt_space}{opt_quote}{password}{opt_quote}'.format(
30+
begin=begin,
31+
opt_quote=opt_quote,
32+
opt_db=opt_db,
33+
opt_dash_undrscr=opt_dash_undrscr,
34+
password_keyword=password_keyword,
35+
opt_space=opt_space,
36+
assignment=assignment,
37+
password=password,
38+
), flags=re.IGNORECASE,
39+
),
40+
)
41+
42+
username_keyword_regex = r'(?:user|user(?:_|-|)name|uid|user(?:_|-|)id|u(?:_|-|)name)'
43+
username_regex = r'([a-zA-Z0-9_]+)'
44+
45+
database_keyword_regex = r'(?:database|db|database(?:_|-|)name|db(?:_|-|)name)'
46+
database_regex = r'([a-zA-Z0-9_-]+)'
47+
48+
port_keyword_regex = r'(?:port|port(?:_|-|)number)'
49+
port_regex = r'([0-9]{1,5})'
50+
51+
hostname_keyword_regex = (
52+
r'(?:host|host(?:_|-|)name|host(?:_|-|)address|'
53+
r'host(?:_|-|)ip|host(?:_|-|)ip(?:_|-|)address)'
54+
)
55+
hostname_regex = (
56+
r'((?:(?:[a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)'
57+
r'*(?:.\[A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9]))'
58+
)
59+
60+
def verify(self, token, content, potential_secret, timeout=5):
61+
62+
username_matches = find_other_factor(
63+
content, self.username_keyword_regex,
64+
self.username_regex,
65+
)
66+
if not username_matches:
67+
return VerifiedResult.UNVERIFIED
68+
69+
database_matches = find_other_factor(
70+
content, self.database_keyword_regex,
71+
self.database_regex,
72+
)
73+
port_matches = find_other_factor(
74+
content, self.port_keyword_regex,
75+
self.port_regex,
76+
)
77+
hostname_matches = find_other_factor(
78+
content, self.hostname_keyword_regex,
79+
self.hostname_regex,
80+
)
81+
82+
url_matches = get_hostname_port_database_from_url(
83+
content, self.hostname_regex, self.port_regex, self.database_regex,
84+
)
85+
for match in url_matches:
86+
hostname, port, database = match
87+
hostname_matches.append(hostname)
88+
port_matches.append(port)
89+
database_matches.append(database)
90+
91+
if not database_matches or not port_matches or not hostname_matches:
92+
return VerifiedResult.UNVERIFIED
93+
94+
for username in username_matches: # pragma: no cover
95+
for database in database_matches: # pragma: no cover
96+
for port in port_matches: # pragma: no cover
97+
for hostname in hostname_matches: # pragma: no cover
98+
verify_result = verify_db2_credentials(
99+
database, hostname, port, username, token, timeout,
100+
)
101+
if verify_result == VerifiedResult.VERIFIED_TRUE:
102+
potential_secret.other_factors['database'] = database
103+
potential_secret.other_factors['hostname'] = hostname
104+
potential_secret.other_factors['port'] = port
105+
potential_secret.other_factors['username'] = username
106+
return verify_result
107+
108+
return VerifiedResult.VERIFIED_FALSE
109+
110+
111+
def verify_db2_credentials(
112+
database, hostname, port, username, password, timeout=5,
113+
): # pragma: no cover
114+
try:
115+
conn_str = 'database={database};hostname={hostname};port={port};' + \
116+
'protocol=tcpip;uid={username};pwd={password};' + \
117+
'ConnectTimeout={timeout}'
118+
conn_str = conn_str.format(
119+
database=database,
120+
hostname=hostname,
121+
port=port,
122+
username=username,
123+
password=password,
124+
timeout=timeout,
125+
)
126+
ibm_db_conn = ibm_db.connect(conn_str, '', '')
127+
if ibm_db_conn:
128+
return VerifiedResult.VERIFIED_TRUE
129+
else:
130+
return VerifiedResult.VERIFIED_FALSE
131+
except Exception as e:
132+
if 'Timeout' in str(e):
133+
return VerifiedResult.UNVERIFIED
134+
else:
135+
return VerifiedResult.VERIFIED_FALSE
136+
137+
138+
def find_other_factor(content, factor_keyword_regex, factor_regex):
139+
begin = r'(?:(?<=\W)|(?<=^))'
140+
opt_quote = r'(?:"|\'|)'
141+
opt_db = r'(?:db2|dashdb|db|)'
142+
opt_dash_undrscr = r'(?:_|-|)'
143+
opt_space = r'(?: *)'
144+
assignment = r'(?:=|:|:=|=>|::)'
145+
regex = re.compile(
146+
r'{begin}{opt_quote}{opt_db}{opt_dash_undrscr}{factor_keyword}{opt_quote}{opt_space}'
147+
'{assignment}{opt_space}{opt_quote}{factor}{opt_quote}'.format(
148+
begin=begin,
149+
opt_quote=opt_quote,
150+
opt_db=opt_db,
151+
opt_dash_undrscr=opt_dash_undrscr,
152+
factor_keyword=factor_keyword_regex,
153+
opt_space=opt_space,
154+
assignment=assignment,
155+
factor=factor_regex,
156+
), flags=re.IGNORECASE,
157+
)
158+
159+
return [
160+
match
161+
for line in content.splitlines()
162+
for match in regex.findall(line)
163+
]
164+
165+
166+
def get_hostname_port_database_from_url(content, hostname_regex, port_regex, database_regex):
167+
"""
168+
Gets hostname, port, and database factors from a jdbc db2 url
169+
Accepts: content to scan, regexes to capture hostname, port, and database
170+
Returns: list of tuples of format (hostname, port, database),
171+
or empty list if no matches
172+
"""
173+
regex = re.compile(
174+
r'jdbc:db2:\/\/{hostname}:{port}\/{database}'.format(
175+
hostname=hostname_regex,
176+
port=port_regex,
177+
database=database_regex,
178+
),
179+
)
180+
181+
return [
182+
(match[0], match[1], match[2])
183+
for line in content.splitlines()
184+
for match in regex.findall(line)
185+
]

‎detect_secrets/plugins/gh.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ class GHDetector(RegexBasedDetector):
1313
secret_type = 'GitHub Credentials'
1414

1515
opt_github = r'(?:github|gh|ghe|git|)'
16-
opt_space = r'(?: |)'
16+
opt_space = r'(?: *)'
1717
opt_quote = r'(?:"|\'|)'
1818
opt_assignment = r'(?:=|:|:=|=>|)'
1919
opt_dash_undrscr = r'(?:_|-|)'

‎detect_secrets/plugins/softlayer.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ class SoftLayerDetector(RegexBasedDetector):
1616
opt_dash_undrscr = r'(?:_|-|)'
1717
opt_api = r'(?:api|)'
1818
key_or_pass = r'(?:key|pwd|password|pass|token)'
19-
opt_space = r'(?: |)'
19+
opt_space = r'(?: *)'
2020
opt_assignment = r'(?:=|:|:=|=>|)'
2121
secret = r'([a-z0-9]{64})'
2222
denylist = [

‎requirements-dev.txt

+1
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,4 @@ tox-pip-extensions
1212
tox>=3.8
1313
unidiff
1414
responses
15+
ibm_db

‎setup.py

+1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
install_requires=[
1919
'pyyaml',
2020
'requests',
21+
'ibm_db',
2122
],
2223
extras_require={
2324
'word_list': [

‎tests/core/usage_test.py

+1
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ def test_consolidates_output_basic(self):
3636
'ArtifactoryDetector': {},
3737
'GHDetector': {},
3838
'SoftLayerDetector': {},
39+
'DB2Detector': {},
3940
}
4041

4142
def test_consolidates_removes_disabled_plugins(self):

‎tests/main_test.py

+16
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,7 @@ def test_scan_string_basic_default(
211211
AWSKeyDetector : False
212212
ArtifactoryDetector: False
213213
BasicAuthDetector : False
214+
DB2Detector : False
214215
GHDetector : False
215216
PrivateKeyDetector : False
216217
SlackDetector : False
@@ -355,6 +356,9 @@ def test_old_baseline_ignored_with_update_flag(
355356
{
356357
'name': 'BasicAuthDetector',
357358
},
359+
{
360+
'name': 'DB2Detector',
361+
},
358362
{
359363
'name': 'GHDetector',
360364
},
@@ -398,6 +402,9 @@ def test_old_baseline_ignored_with_update_flag(
398402
{
399403
'name': 'BasicAuthDetector',
400404
},
405+
{
406+
'name': 'DB2Detector',
407+
},
401408
{
402409
'name': 'GHDetector',
403410
},
@@ -498,6 +505,9 @@ def test_old_baseline_ignored_with_update_flag(
498505
{
499506
'name': 'BasicAuthDetector',
500507
},
508+
{
509+
'name': 'DB2Detector',
510+
},
501511
{
502512
'name': 'GHDetector',
503513
},
@@ -540,6 +550,9 @@ def test_old_baseline_ignored_with_update_flag(
540550
{
541551
'name': 'BasicAuthDetector',
542552
},
553+
{
554+
'name': 'DB2Detector',
555+
},
543556
{
544557
'name': 'GHDetector',
545558
},
@@ -681,6 +694,9 @@ def test_scan_with_default_plugin(self):
681694
{
682695
'name': 'BasicAuthDetector',
683696
},
697+
{
698+
'name': 'DB2Detector',
699+
},
684700
{
685701
'name': 'GHDetector',
686702
},

‎tests/plugins/db2_test.py

+299
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,299 @@
1+
from __future__ import absolute_import
2+
3+
import textwrap
4+
5+
import pytest
6+
from mock import MagicMock
7+
from mock import patch
8+
9+
from detect_secrets.core.constants import VerifiedResult
10+
from detect_secrets.core.potential_secret import PotentialSecret
11+
from detect_secrets.plugins.db2 import DB2Detector
12+
from detect_secrets.plugins.db2 import find_other_factor
13+
from detect_secrets.plugins.db2 import get_hostname_port_database_from_url
14+
15+
16+
DB2_USER = 'fake_user'
17+
DB2_PASSWORD = 'fake_password'
18+
DB2_PORT = '1234'
19+
DB2_HOSTNAME = 'fake.host.name'
20+
DB2_DATABASE = 'fake_database'
21+
DB2_CONN_STRING = 'database={DB2_DATABASE};hostname={DB2_HOSTNAME};port={DB2_PORT};' + \
22+
'protocol=tcpip;uid={DB2_USER};pwd={DB2_PASSWORD};ConnectTimeout=5'
23+
DB2_CONN_STRING = DB2_CONN_STRING.format(
24+
DB2_DATABASE=DB2_DATABASE,
25+
DB2_HOSTNAME=DB2_HOSTNAME,
26+
DB2_PORT=DB2_PORT,
27+
DB2_USER=DB2_USER,
28+
DB2_PASSWORD=DB2_PASSWORD,
29+
)
30+
31+
32+
class TestGHDetector(object):
33+
34+
@pytest.mark.parametrize(
35+
'token, payload, should_flag',
36+
[
37+
(
38+
'secret',
39+
'database=test;hostname=host.test.com;'
40+
'port=1;protocol=tcpip;uid=testid;pwd=secret', True,
41+
),
42+
('$omespeci@!ch@r$', 'dbpwd=$omespeci@!ch@r$', True),
43+
('astring', 'db2_password = "astring"', True),
44+
('Iusedb2!', '"password": "Iusedb2!"', True),
45+
('ilikespaces', 'password = "ilikespaces"', True),
46+
(':anothersyntax!', 'pwd::anothersyntax!', True),
47+
('@#!%#', 'DB2_PASSWORD = "@#!%#"', True),
48+
('pass', 'dashdb-password = "pass"', True),
49+
('', 'dashdb_host = notapassword', False),
50+
('', 'someotherpassword = "doesnt start right"', False),
51+
],
52+
)
53+
def test_analyze_string(self, token, payload, should_flag):
54+
logic = DB2Detector()
55+
56+
output = logic.analyze_string(payload, 1, 'mock_filename')
57+
assert len(output) == int(should_flag)
58+
if len(output) > 0:
59+
assert list(output.keys())[0].secret == token
60+
61+
@patch('detect_secrets.plugins.db2.ibm_db.connect')
62+
def test_verify_invalid_connect_returns_none(self, mock_db2_connect):
63+
mock_db2_connect.return_value = None
64+
65+
potential_secret = PotentialSecret('test db2', 'test filename', DB2_PASSWORD)
66+
assert DB2Detector().verify(
67+
DB2_PASSWORD,
68+
'''user={},
69+
password={},
70+
database={},
71+
host={},
72+
port={}'''.format(DB2_USER, DB2_PASSWORD, DB2_DATABASE, DB2_HOSTNAME, DB2_PORT),
73+
potential_secret,
74+
) == VerifiedResult.VERIFIED_FALSE
75+
76+
mock_db2_connect.assert_called_with(DB2_CONN_STRING, '', '')
77+
78+
@patch('detect_secrets.plugins.db2.ibm_db.connect')
79+
def test_verify_invalid_connect_throws_exception(self, mock_db2_connect):
80+
mock_db2_connect.side_effect = Exception('oops')
81+
82+
potential_secret = PotentialSecret('test db2', 'test filename', DB2_PASSWORD)
83+
assert DB2Detector().verify(
84+
DB2_PASSWORD,
85+
'''user={},
86+
password={},
87+
database={},
88+
host={},
89+
port={}'''.format(DB2_USER, DB2_PASSWORD, DB2_DATABASE, DB2_HOSTNAME, DB2_PORT),
90+
potential_secret,
91+
) == VerifiedResult.VERIFIED_FALSE
92+
93+
mock_db2_connect.assert_called_with(DB2_CONN_STRING, '', '')
94+
95+
@patch('detect_secrets.plugins.db2.ibm_db.connect')
96+
def test_verify_valid_secret(self, mock_db2_connect):
97+
mock_db2_connect.return_value = MagicMock()
98+
99+
potential_secret = PotentialSecret('test db2', 'test filename', DB2_PASSWORD)
100+
assert DB2Detector().verify(
101+
DB2_PASSWORD,
102+
'''user={},
103+
password={},
104+
database={},
105+
host={},
106+
port={}'''.format(DB2_USER, DB2_PASSWORD, DB2_DATABASE, DB2_HOSTNAME, DB2_PORT),
107+
potential_secret,
108+
) == VerifiedResult.VERIFIED_TRUE
109+
110+
mock_db2_connect.assert_called_with(DB2_CONN_STRING, '', '')
111+
assert potential_secret.other_factors['database'] == DB2_DATABASE
112+
assert potential_secret.other_factors['hostname'] == DB2_HOSTNAME
113+
assert potential_secret.other_factors['port'] == DB2_PORT
114+
assert potential_secret.other_factors['username'] == DB2_USER
115+
116+
@patch('detect_secrets.plugins.db2.ibm_db.connect')
117+
def test_verify_valid_secret_in_single_quotes(self, mock_db2_connect):
118+
mock_db2_connect.return_value = MagicMock()
119+
120+
potential_secret = PotentialSecret('test db2', 'test filename', DB2_PASSWORD)
121+
assert DB2Detector().verify(
122+
DB2_PASSWORD,
123+
'''user='{}',
124+
password='{}',
125+
database='{}',
126+
host='{}',
127+
port='{}'
128+
'''.format(DB2_USER, DB2_PASSWORD, DB2_DATABASE, DB2_HOSTNAME, DB2_PORT),
129+
potential_secret,
130+
) == VerifiedResult.VERIFIED_TRUE
131+
132+
mock_db2_connect.assert_called_with(DB2_CONN_STRING, '', '')
133+
assert potential_secret.other_factors['database'] == DB2_DATABASE
134+
assert potential_secret.other_factors['hostname'] == DB2_HOSTNAME
135+
assert potential_secret.other_factors['port'] == DB2_PORT
136+
assert potential_secret.other_factors['username'] == DB2_USER
137+
138+
@patch('detect_secrets.plugins.db2.ibm_db.connect')
139+
def test_verify_valid_secret_in_double_quotes(self, mock_db2_connect):
140+
mock_db2_connect.return_value = MagicMock()
141+
142+
potential_secret = PotentialSecret('test db2', 'test filename', DB2_PASSWORD)
143+
assert DB2Detector().verify(
144+
DB2_PASSWORD,
145+
'''user="{}",
146+
password="{}",
147+
database="{}",
148+
host="{}",
149+
port="{}"
150+
'''.format(DB2_USER, DB2_PASSWORD, DB2_DATABASE, DB2_HOSTNAME, DB2_PORT),
151+
potential_secret,
152+
) == VerifiedResult.VERIFIED_TRUE
153+
154+
mock_db2_connect.assert_called_with(DB2_CONN_STRING, '', '')
155+
assert potential_secret.other_factors['database'] == DB2_DATABASE
156+
assert potential_secret.other_factors['hostname'] == DB2_HOSTNAME
157+
assert potential_secret.other_factors['port'] == DB2_PORT
158+
assert potential_secret.other_factors['username'] == DB2_USER
159+
160+
@patch('detect_secrets.plugins.db2.ibm_db.connect')
161+
def test_verify_from_url(self, mock_db2_connect):
162+
mock_db2_connect.return_value = MagicMock()
163+
164+
potential_secret = PotentialSecret('test db2', 'test filename', DB2_PASSWORD)
165+
assert DB2Detector().verify(
166+
DB2_PASSWORD,
167+
'''user={},
168+
password={},
169+
url=jdbc:db2://{}:{}/{},
170+
'''.format(DB2_USER, DB2_PASSWORD, DB2_HOSTNAME, DB2_PORT, DB2_DATABASE),
171+
potential_secret,
172+
) == VerifiedResult.VERIFIED_TRUE
173+
174+
mock_db2_connect.assert_called_with(DB2_CONN_STRING, '', '')
175+
assert potential_secret.other_factors['database'] == DB2_DATABASE
176+
assert potential_secret.other_factors['hostname'] == DB2_HOSTNAME
177+
assert potential_secret.other_factors['port'] == DB2_PORT
178+
assert potential_secret.other_factors['username'] == DB2_USER
179+
180+
@patch('detect_secrets.plugins.db2.ibm_db.connect')
181+
def test_verify_times_out(self, mock_db2_connect):
182+
mock_db2_connect.side_effect = Exception('Timeout')
183+
184+
potential_secret = PotentialSecret('test db2', 'test filename', DB2_PASSWORD)
185+
assert DB2Detector().verify(
186+
DB2_PASSWORD,
187+
'''user={},
188+
password={},
189+
database={},
190+
host={},
191+
port={}'''.format(DB2_USER, DB2_PASSWORD, DB2_DATABASE, DB2_HOSTNAME, DB2_PORT),
192+
potential_secret,
193+
) == VerifiedResult.UNVERIFIED
194+
195+
mock_db2_connect.assert_called_with(DB2_CONN_STRING, '', '')
196+
197+
def test_verify_no_other_factors(self):
198+
potential_secret = PotentialSecret('test db2', 'test filename', DB2_PASSWORD)
199+
assert DB2Detector().verify(
200+
DB2_PASSWORD,
201+
'password={}'.format(DB2_PASSWORD),
202+
potential_secret,
203+
) == VerifiedResult.UNVERIFIED
204+
205+
206+
@pytest.mark.parametrize(
207+
'content, factor_keyword_regex, factor_regex, expected_output',
208+
(
209+
(
210+
textwrap.dedent("""
211+
user = {}
212+
""")[1:-1].format(
213+
DB2_USER,
214+
),
215+
DB2Detector().username_keyword_regex,
216+
DB2Detector().username_regex,
217+
[DB2_USER],
218+
),
219+
(
220+
textwrap.dedent("""
221+
port = {}
222+
""")[1:-1].format(
223+
DB2_PORT,
224+
),
225+
DB2Detector().port_keyword_regex,
226+
DB2Detector().port_regex,
227+
[DB2_PORT],
228+
),
229+
(
230+
textwrap.dedent("""
231+
database = {}
232+
""")[1:-1].format(
233+
DB2_DATABASE,
234+
),
235+
DB2Detector().database_keyword_regex,
236+
DB2Detector().database_regex,
237+
[DB2_DATABASE],
238+
),
239+
(
240+
textwrap.dedent("""
241+
host = {}
242+
""")[1:-1].format(
243+
DB2_HOSTNAME,
244+
),
245+
DB2Detector().hostname_keyword_regex,
246+
DB2Detector().hostname_regex,
247+
[DB2_HOSTNAME],
248+
),
249+
),
250+
)
251+
def test_find_other_factor(content, factor_keyword_regex, factor_regex, expected_output):
252+
assert find_other_factor(content, factor_keyword_regex, factor_regex) == expected_output
253+
254+
255+
@pytest.mark.parametrize(
256+
'content, hostname_regex, port_regex, database_regex, expected_output',
257+
(
258+
(
259+
textwrap.dedent("""
260+
jdbc:db2://{}:{}/{}
261+
""")[1:-1].format(
262+
DB2_HOSTNAME,
263+
DB2_PORT,
264+
DB2_DATABASE,
265+
),
266+
DB2Detector().hostname_regex,
267+
DB2Detector().port_regex,
268+
DB2Detector().database_regex,
269+
[(DB2_HOSTNAME, DB2_PORT, DB2_DATABASE)],
270+
),
271+
(
272+
textwrap.dedent("""
273+
jdbc:db2://{}:{}/
274+
""")[1:-1].format(
275+
DB2_HOSTNAME,
276+
DB2_PORT,
277+
),
278+
DB2Detector().hostname_regex,
279+
DB2Detector().port_regex,
280+
DB2Detector().database_regex,
281+
[],
282+
),
283+
(
284+
textwrap.dedent("""
285+
nonsense
286+
"""),
287+
DB2Detector().hostname_regex,
288+
DB2Detector().port_regex,
289+
DB2Detector().database_regex,
290+
[],
291+
),
292+
),
293+
)
294+
def test_get_hostname_port_database_from_url(
295+
content, hostname_regex, port_regex, database_regex, expected_output,
296+
):
297+
assert get_hostname_port_database_from_url(
298+
content, hostname_regex, port_regex, database_regex,
299+
) == expected_output

‎tests/pre_commit_hook_test.py

+3
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,9 @@ def test_baseline_gets_updated(
244244
{
245245
'name': 'BasicAuthDetector',
246246
},
247+
{
248+
'name': 'DB2Detector',
249+
},
247250
{
248251
'name': 'GHDetector',
249252
},

0 commit comments

Comments
 (0)
Please sign in to comment.