Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a (b)ack option to 'Is this a valid secret?' Closes Issue #63 #72

Merged
merged 8 commits into from
Sep 11, 2018
15 changes: 12 additions & 3 deletions detect_secrets/core/audit.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from ..plugins.core import initialize
from ..plugins.high_entropy_strings import HighEntropyStringsPlugin
from .baseline import merge_results
from .bidirectional_iterator import BidirectionalIterator
from .color import BashColor
from .color import Color
from .potential_secret import PotentialSecret
Expand All @@ -29,7 +30,8 @@ def audit_baseline(baseline_filename):

current_secret_index = 0
results = defaultdict(list)
for filename, secret, total in _secret_generator(original_baseline):
secret_iterator = BidirectionalIterator(list(_secret_generator(original_baseline)))
for filename, secret, total in secret_iterator:
_clear_screen()

if 'is_secret' not in secret:
Expand All @@ -43,7 +45,7 @@ def audit_baseline(baseline_filename):
total,
original_baseline['plugins_used'],
)
decision = _get_user_decision()
decision = _get_user_decision(can_step_back=secret_iterator.can_step_back())
except SecretNotFoundOnSpecifiedLineError:
decision = _get_user_decision(prompt_secret_decision=False)
else:
Expand All @@ -55,6 +57,9 @@ def audit_baseline(baseline_filename):
print('Quitting...')
break

if decision == 'b':
secret_iterator.step_back_on_next_iteration()

_handle_user_decision(decision, secret)
results[filename].append(secret)

Expand Down Expand Up @@ -174,14 +179,16 @@ def _print_context(filename, secret, count, total, plugin_settings): # pragma:
raise error_obj


def _get_user_decision(prompt_secret_decision=True):
def _get_user_decision(prompt_secret_decision=True, can_step_back=False):
"""
:type prompt_secret_decision: bool
:param prompt_secret_decision: if False, won't ask to label secret.
"""
allowable_user_input = ['s', 'q']
if prompt_secret_decision:
allowable_user_input.extend(['y', 'n'])
if can_step_back:
allowable_user_input.append('b')

user_input = None
while user_input not in allowable_user_input:
Expand All @@ -192,6 +199,8 @@ def _get_user_decision(prompt_secret_decision=True):
user_input_string = 'Is this a valid secret? (y)es, (n)o, '
else:
user_input_string = 'What would you like to do? '
if 'b' in allowable_user_input:
user_input_string += '(b)ack, '
user_input_string += '(s)kip, (q)uit: '

user_input = input(user_input_string)
Expand Down
31 changes: 31 additions & 0 deletions detect_secrets/core/bidirectional_iterator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
class BidirectionalIterator(object):
def __init__(self, collection):
self.collection = collection
self.index = -1 # starts on -1, as index is increased _before_ getting result
self.step_back_once = False

def __next__(self):
if self.step_back_once:
self.index -= 1
self.step_back_once = False
else:
self.index += 1
if self.index < 0:
raise StopIteration
try:
result = self.collection[self.index]
cleborys marked this conversation as resolved.
Show resolved Hide resolved
except IndexError:
raise StopIteration
return result

def next(self):
Copy link
Collaborator

@KevinHock KevinHock Sep 4, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Looping should call the __next__ method directly, so no need for a next method 👍

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added next to be python2 compatible (it was renamed form next to __next__ from python2 to 3).
However, this does not feel very clean - perhaps you know of a better way? :)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Aha! Very good point, I only tested on Python 3. My bad. I am impressed by how thorough you are 👍

return self.__next__()

def step_back_on_next_iteration(self):
self.step_back_once = True

def can_step_back(self):
return self.index > 0

def __iter__(self):
return self
62 changes: 62 additions & 0 deletions tests/core/bidirectional_iterator_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
from __future__ import absolute_import

import pytest

from detect_secrets.core import bidirectional_iterator


class TestBidirectionalIterator(object):

def test_no_input(self):
iterator = bidirectional_iterator.BidirectionalIterator([])
with pytest.raises(StopIteration):
iterator.__next__()

def test_cannot_step_back_too_far(self):
iterator = bidirectional_iterator.BidirectionalIterator([0])
iterator.step_back_on_next_iteration()
with pytest.raises(StopIteration):
iterator.__next__()

def test_cannot_step_back_too_far_after_stepping_in(self):
iterator = bidirectional_iterator.BidirectionalIterator([0, 1, 2])
for _ in range(3):
iterator.__next__()
for _ in range(2):
iterator.step_back_on_next_iteration()
iterator.__next__()
iterator.step_back_on_next_iteration()
with pytest.raises(StopIteration):
iterator.__next__()

def test_works_correctly_in_loop(self):
iterator = bidirectional_iterator.BidirectionalIterator([0, 1, 2, 3, 4, 5])
commands = [0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0]
command_count = 0
results = []
for index in iterator:
if commands[command_count]:
iterator.step_back_on_next_iteration()
results.append(index)
command_count += 1
assert results == [0, 1, 0, 1, 2, 1, 0, 1, 2, 3, 4, 3, 2, 3, 4, 5]

def test_normal_iterator_if_not_told_to_step_back(self):
input_list = [0, 1, 2, 3, 4, 5]
iterator = bidirectional_iterator.BidirectionalIterator(input_list)
results = []
for index in iterator:
results.append(index)
assert results == input_list

def test_knows_when_stepping_back_possible(self):
iterator = bidirectional_iterator.BidirectionalIterator([0, 1, 2, 3])
commands = [0, 1, 0, 0, 1, 1, 0, 0, 0, 0]
command_count = 0
results = []
for _ in iterator:
if commands[command_count]:
iterator.step_back_on_next_iteration()
results.append(iterator.can_step_back())
command_count += 1
assert results == [False, True, False, True, True, True, False, True, True, True]