From c5096e6a62120b7a690f7d03df450952a038f76d Mon Sep 17 00:00:00 2001 From: John A Edwards Date: Tue, 8 Oct 2019 20:31:16 +0100 Subject: [PATCH] Cloudant 2 (#207) * clarify var names in test * help text * correcting account / pw test verification path * correcting validation error response * change detector vars & add cases * detection tests clean pending verification tests * all tests clean * added more key assignment tests * PR simplifications * fix capture group * fix capture group 2 --- cloudant.py | 45 +++++++++++++++++++++------------- cloudant_test.py | 64 +++++++++++++++++++++++++++++++----------------- 2 files changed, 70 insertions(+), 39 deletions(-) diff --git a/cloudant.py b/cloudant.py index 04bb2fb9..41a3143d 100644 --- a/cloudant.py +++ b/cloudant.py @@ -17,37 +17,48 @@ class CloudantDetector(RegexBasedDetector): opt_dashes = r'(?:--|)' opt_dot = r'(?:\.|)' dot = r'\.' - cl_account = r'[0-9a-z\-\_]*' - cl = r'(cloudant|cl|clou)' + cl_account = r'[0-9a-z\-\_]+' + cl = r'(?:cloudant|cl|clou)' opt_dash_undrscr = r'(?:_|-|)' opt_api = r'(?:api|)' - cl_key_or_pass = cl + opt_dash_undrscr + r'(?:key|pwd|pw|password|pass|token)' + cl_key_or_pass = opt_api + r'(?:key|pwd|pw|password|pass|token)' opt_space = r'(?: |)' assignment = r'(?:=|:|:=|=>)' - cl_secret = r'[0-9a-f]{64}' + cl_pw = r'([0-9a-f]{64})' + cl_api_key = r'([a-z]{24})' colon = r'\:' at = r'\@' - http = r'(?:http\:\/\/|https\:\/\/)' + http = r'(?:https?\:\/\/)' cloudant_api_url = r'cloudant\.com' denylist = [ + RegexBasedDetector.assign_regex_generator( + prefix_regex=cl, + password_keyword_regex=cl_key_or_pass, + password_regex=cl_pw, + ), + RegexBasedDetector.assign_regex_generator( + prefix_regex=cl, + password_keyword_regex=cl_key_or_pass, + password_regex=cl_api_key, + ), re.compile( - r'{cl_key_or_pass}{opt_space}{assignment}{opt_space}{opt_quote}{cl_secret}'.format( - cl_key_or_pass=cl_key_or_pass, - opt_quote=opt_quote, + r'{http}{cl_account}{colon}{cl_pw}{at}{cl_account}{dot}{cloudant_api_url}'.format( + http=http, + colon=colon, cl_account=cl_account, - opt_dash_undrscr=opt_dash_undrscr, - opt_api=opt_api, - opt_space=opt_space, - assignment=assignment, - cl_secret=cl_secret, - ), flags=re.IGNORECASE, + cl_pw=cl_pw, + at=at, + dot=dot, + cloudant_api_url=cloudant_api_url, + ), + flags=re.IGNORECASE, ), re.compile( - r'{http}{cl_account}{colon}{cl_secret}{at}{cl_account}{dot}{cloudant_api_url}'.format( + r'{http}{cl_account}{colon}{cl_api_key}{at}{cl_account}{dot}{cloudant_api_url}'.format( http=http, colon=colon, cl_account=cl_account, - cl_secret=cl_secret, + cl_api_key=cl_api_key, at=at, dot=dot, cloudant_api_url=cloudant_api_url, @@ -105,7 +116,7 @@ def verify_cloudant_key(hostname, token, potential_secret=None): request_url = 'https://{hostname}:' \ '{token}' \ '@{hostname}.' \ - 'cloudant.com/_api/v2'.format( + 'cloudant.com'.format( hostname=hostname, token=token, ) diff --git a/cloudant_test.py b/cloudant_test.py index 74d20cea..d815f71e 100644 --- a/cloudant_test.py +++ b/cloudant_test.py @@ -11,8 +11,11 @@ from detect_secrets.plugins.cloudant import get_host CL_HOST = 'testy_test' # also called user -# only detecting 64 hex -CL_TOKEN = 'abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234' +# only detecting 64 hex CL generated password +CL_PW = 'abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234' + +# detecting 24 alpha for CL generated API KEYS +CL_API_KEY = 'abcdefghijabcdefghijabcd' class TestCloudantDetector(object): @@ -21,25 +24,42 @@ class TestCloudantDetector(object): 'payload, should_flag', [ ( - 'https://{cl_host}:{cl_token}@{cl_host}.cloudant.com"'.format( - cl_host=CL_HOST, cl_token=CL_TOKEN, + 'https://{cl_host}:{cl_pw}@{cl_host}.cloudant.com"'.format( + cl_host=CL_HOST, cl_pw=CL_PW, + ), True, + ), + ( + 'https://{cl_host}:{cl_pw}@{cl_host}.cloudant.com/_api/v2/'.format( + cl_host=CL_HOST, cl_pw=CL_PW, + ), True, + ), + ( + 'https://{cl_host}:{cl_pw}@{cl_host}.cloudant.com/_api/v2/'.format( + cl_host=CL_HOST, cl_pw=CL_PW, + ), True, + ), + ( + 'https://{cl_host}:{cl_pw}@{cl_host}.cloudant.com'.format( + cl_host=CL_HOST, cl_pw=CL_PW, ), True, ), ( - 'https://{cl_host}:{cl_token}@{cl_host}.cloudant.com/_api/v2/'.format( - cl_host=CL_HOST, cl_token=CL_TOKEN, + 'https://{cl_host}:{cl_api_key}@{cl_host}.cloudant.com'.format( + cl_host=CL_HOST, cl_api_key=CL_API_KEY, ), True, ), ( - 'https://{cl_host}:{cl_token}.cloudant.com'.format( - cl_host=CL_HOST, cl_token=CL_TOKEN, + 'https://{cl_host}:{cl_pw}.cloudant.com'.format( + cl_host=CL_HOST, cl_pw=CL_PW, ), False, ), - ('cloudant_password=\'{cl_token}\''.format(cl_token=CL_TOKEN), True), - ('cloudant_pw=\'{cl_token}\''.format(cl_token=CL_TOKEN), True), - ('cloudant_pw="{cl_token}"'.format(cl_token=CL_TOKEN), True), - ('clou_pw = "{cl_token}"'.format(cl_token=CL_TOKEN), True), + ('cloudant_password=\'{cl_pw}\''.format(cl_pw=CL_PW), True), + ('cloudant_pw=\'{cl_pw}\''.format(cl_pw=CL_PW), True), + ('cloudant_pw="{cl_pw}"'.format(cl_pw=CL_PW), True), + ('clou_pw = "{cl_pw}"'.format(cl_pw=CL_PW), True), + ('cloudant_key = "{cl_api_key}"'.format(cl_api_key=CL_API_KEY), True), ('cloudant_password = "a-fake-tooshort-key"', False), + ('cl_api_key = "a-fake-api-key"', False), ], ) def test_analyze_string(self, payload, should_flag): @@ -50,31 +70,31 @@ def test_analyze_string(self, payload, should_flag): @responses.activate def test_verify_invalid_secret(self): - cl_api_url = 'https://{cl_host}:{cl_token}@{cl_host}.cloudant.com/_api/v2'.format( - cl_host=CL_HOST, cl_token=CL_TOKEN, + cl_api_url = 'https://{cl_host}:{cl_pw}@{cl_host}.cloudant.com'.format( + cl_host=CL_HOST, cl_pw=CL_PW, ) responses.add( responses.GET, cl_api_url, - json={'error': 'Access denied. '}, status=401, + json={'error': 'unauthorized'}, status=401, ) assert CloudantDetector().verify( - CL_TOKEN, + CL_PW, 'cloudant_host={}'.format(CL_HOST), ) == VerifiedResult.VERIFIED_FALSE @responses.activate def test_verify_valid_secret(self): - cl_api_url = 'https://{cl_host}:{cl_token}@{cl_host}.cloudant.com/_api/v2'.format( - cl_host=CL_HOST, cl_token=CL_TOKEN, + cl_api_url = 'https://{cl_host}:{cl_pw}@{cl_host}.cloudant.com'.format( + cl_host=CL_HOST, cl_pw=CL_PW, ) responses.add( responses.GET, cl_api_url, json={'id': 1}, status=200, ) - potential_secret = PotentialSecret('test cloudant', 'test filename', CL_TOKEN) + potential_secret = PotentialSecret('test cloudant', 'test filename', CL_PW) assert CloudantDetector().verify( - CL_TOKEN, + CL_PW, 'cloudant_host={}'.format(CL_HOST), potential_secret, ) == VerifiedResult.VERIFIED_TRUE @@ -83,13 +103,13 @@ def test_verify_valid_secret(self): @responses.activate def test_verify_unverified_secret(self): assert CloudantDetector().verify( - CL_TOKEN, + CL_PW, 'cloudant_host={}'.format(CL_HOST), ) == VerifiedResult.UNVERIFIED def test_verify_no_secret(self): assert CloudantDetector().verify( - CL_TOKEN, + CL_PW, 'no_un={}'.format(CL_HOST), ) == VerifiedResult.UNVERIFIED