Skip to content

Commit 0de1f29

Browse files
author
Juliya Smith
authored
Bugfix/defaultp not used (#15)
1 parent afb8a7d commit 0de1f29

19 files changed

+432
-222
lines changed

CHANGELOG.md

+13
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
88
The intended audience of this file is for py42 consumers -- as such, changes that don't affect
99
how a consumer would use the library (e.g. adding unit tests, updating documentation, etc) are not captured here.
1010

11+
## 0.4.1 - 2020-03-13
12+
13+
### Fixed
14+
15+
- Bug where `profile reset-pw` did not work with the default profile.
16+
- Bug where `profile show` indicated a password was set for a different profile.
17+
- We now validate credentials when setting a password.
18+
19+
20+
### Changed
21+
22+
- Date inputs are now required to be in quotes when they include a time.
23+
1124
## 0.4.0 - 2020-03-12
1225

1326
### Added

src/code42cli/__version__.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = "0.4.0"
1+
__version__ = "0.4.1"

src/code42cli/profile/password.py

+17-15
Original file line numberDiff line numberDiff line change
@@ -9,30 +9,32 @@
99
_ROOT_SERVICE_NAME = u"code42cli"
1010

1111

12-
def get_password(profile_name):
13-
"""Gets your currently stored password for your profile."""
14-
accessor = get_config_accessor()
15-
profile = accessor.get_profile(profile_name)
16-
service_name = _get_service_name(profile_name)
12+
def get_stored_password(profile_name):
13+
"""Gets your currently stored password for the given profile name."""
14+
profile = _get_profile(profile_name)
15+
service_name = _get_service_name(profile.name)
1716
username = _get_username(profile)
1817
password = keyring.get_password(service_name, username)
1918
return password
2019

2120

22-
def set_password_from_prompt(profile_name):
23-
"""Prompts and sets your password for your profile."""
24-
password = getpass()
25-
accessor = get_config_accessor()
26-
profile = accessor.get_profile(profile_name)
27-
service_name = _get_service_name(profile_name)
21+
def get_password_from_prompt():
22+
"""Prompts you and returns what you input."""
23+
return getpass()
24+
25+
26+
def set_password(profile_name, new_password):
27+
"""Sets your password for the given profile name."""
28+
profile = _get_profile(profile_name)
29+
service_name = _get_service_name(profile.name)
2830
username = _get_username(profile)
29-
keyring.set_password(service_name, username, password)
31+
keyring.set_password(service_name, username, new_password)
3032
print(u"'Code42 Password' updated.")
31-
return password
3233

3334

34-
def get_password_from_prompt():
35-
return getpass()
35+
def _get_profile(profile_name):
36+
accessor = get_config_accessor()
37+
return accessor.get_profile(profile_name)
3638

3739

3840
def _get_service_name(profile_name):

src/code42cli/profile/profile.py

+18-8
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
print_set_profile_help,
1111
print_no_existing_profile_message,
1212
)
13+
from code42cli.sdk_client import validate_connection
1314

1415

1516
class Code42Profile(object):
@@ -33,7 +34,7 @@ def ignore_ssl_error(self):
3334
return self._profile[ConfigAccessor.IGNORE_SSL_ERRORS_KEY]
3435

3536
def get_password(self):
36-
pwd = password.get_password(self.name)
37+
pwd = password.get_stored_password(self.name)
3738
if not pwd:
3839
pwd = password.get_password_from_prompt()
3940
return pwd
@@ -91,7 +92,7 @@ def show_profile(args):
9192
print(u"\t* {0} = {1}".format(ConfigAccessor.USERNAME_KEY, profile.username))
9293
print(u"\t* {0} = {1}".format(ConfigAccessor.AUTHORITY_KEY, profile.authority_url))
9394
print(u"\t* {0} = {1}".format(ConfigAccessor.IGNORE_SSL_ERRORS_KEY, profile.ignore_ssl_error))
94-
if password.get_password(args.profile_name) is not None:
95+
if password.get_stored_password(profile.name) is not None:
9596
print(u"\t* A password is set.")
9697
print(u"")
9798

@@ -109,7 +110,15 @@ def set_profile(args):
109110

110111
def prompt_for_password_reset(args):
111112
"""Securely prompts for your password and then stores it using keyring."""
112-
password.set_password_from_prompt(args.profile_name)
113+
profile = get_profile(args.profile_name)
114+
new_password = password.get_password_from_prompt()
115+
if not validate_connection(profile.authority_url, profile.username, new_password):
116+
print_error(
117+
"Your password was not saved because your credentials failed to validate. "
118+
"Check your network connection and the spelling of your username and server URL."
119+
)
120+
exit(1)
121+
password.set_password(profile.name, new_password)
113122

114123

115124
def list_profiles(*args):
@@ -130,7 +139,7 @@ def use_profile(args):
130139
try:
131140
accessor.switch_default_profile(args.profile_name)
132141
except Exception as ex:
133-
print_error(ex)
142+
print_error(str(ex))
134143
exit(1)
135144

136145

@@ -198,9 +207,10 @@ def _verify_args_for_set(args):
198207
missing_values = not args.c42_username and not args.c42_authority_url
199208
if missing_values:
200209
try:
201-
profile = get_profile(args.profile_name)
210+
accessor = get_config_accessor()
211+
profile = Code42Profile(accessor.get_profile(args.profile_name))
202212
missing_values = not profile.username and not profile.authority_url
203-
except SystemExit:
213+
except Exception:
204214
missing_values = True
205215

206216
if missing_values:
@@ -231,10 +241,10 @@ def _missing_default_profile(args):
231241
profile_name_arg_is_none = (
232242
args.profile_name is None or args.profile_name == ConfigAccessor.DEFAULT_VALUE
233243
)
234-
return profile_name_arg_is_none and not _default_profile_exists()
244+
return profile_name_arg_is_none and not _default_profile_exist()
235245

236246

237-
def _default_profile_exists():
247+
def _default_profile_exist():
238248
try:
239249
accessor = get_config_accessor()
240250
profile = Code42Profile(accessor.get_profile())

src/code42cli/sdk_client.py

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
from py42 import debug_level
2+
from py42 import settings
3+
from py42.sdk import SDK
4+
5+
from code42cli.util import print_error
6+
7+
8+
def create_sdk(profile, is_debug_mode):
9+
if is_debug_mode:
10+
settings.debug_level = debug_level.DEBUG
11+
try:
12+
password = profile.get_password()
13+
return SDK.create_using_local_account(profile.authority_url, profile.username, password)
14+
except Exception:
15+
print_error(
16+
u"Invalid credentials or host address. "
17+
u"Verify your profile is set up correctly and that you are supplying the correct password."
18+
)
19+
exit(1)
20+
21+
22+
def validate_connection(authority_url, username, password):
23+
try:
24+
SDK.create_using_local_account(authority_url, username, password)
25+
return True
26+
except:
27+
print(username, password, authority_url)
28+
return False

src/code42cli/securitydata/arguments/search.py

-2
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,6 @@ def _add_begin_date_arg(parser):
7070
parser.add_argument(
7171
u"-b",
7272
u"--begin",
73-
nargs=u"+",
7473
action=u"store",
7574
dest=SearchArguments.BEGIN_DATE,
7675
help=u"The beginning of the date range in which to look for events, "
@@ -82,7 +81,6 @@ def _add_end_date_arg(parser):
8281
parser.add_argument(
8382
u"-e",
8483
u"--end",
85-
nargs=u"+",
8684
action=u"store",
8785
dest=SearchArguments.END_DATE,
8886
help=u"The end of the date range in which to look for events, "

src/code42cli/securitydata/date_helper.py

+9-9
Original file line numberDiff line numberDiff line change
@@ -63,24 +63,24 @@ def _verify_timestamp_order(min_timestamp, max_timestamp):
6363
raise ValueError(u"Begin date cannot be after end date")
6464

6565

66-
def _parse_timestamp(date_tuple):
66+
def _parse_timestamp(date_and_time):
6767
try:
68-
date_str = _join_date_tuple(date_tuple)
69-
date_format = u"%Y-%m-%d" if len(date_tuple) == 1 else u"%Y-%m-%d %H:%M:%S"
68+
date_str = _join_date_and_time(date_and_time)
69+
date_format = u"%Y-%m-%d" if len(date_and_time) == 1 else u"%Y-%m-%d %H:%M:%S"
7070
time = datetime.strptime(date_str, date_format)
7171
except ValueError:
7272
raise ValueError(_FORMAT_VALUE_ERROR_MESSAGE)
7373
return convert_datetime_to_timestamp(time)
7474

7575

76-
def _join_date_tuple(date_tuple):
77-
if not date_tuple:
76+
def _join_date_and_time(date_and_time):
77+
if not date_and_time:
7878
return None
79-
date_str = date_tuple[0]
80-
if len(date_tuple) == 1:
79+
date_str = date_and_time[0]
80+
if len(date_and_time) == 1:
8181
return date_str
82-
if len(date_tuple) == 2:
83-
date_str = "{0} {1}".format(date_str, date_tuple[1])
82+
if len(date_and_time) == 2:
83+
date_str = "{0} {1}".format(date_str, date_and_time[1])
8484
else:
8585
raise ValueError(_FORMAT_VALUE_ERROR_MESSAGE)
8686
return date_str

src/code42cli/securitydata/extraction.py

+8-22
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,6 @@
44

55
from c42eventextractor import FileEventHandlers
66
from c42eventextractor.extractors import FileEventExtractor
7-
from py42 import debug_level
8-
from py42 import settings
9-
from py42.sdk import SDK
107
from py42.sdk.file_event_query.cloud_query import Actor
118
from py42.sdk.file_event_query.device_query import DeviceUsername
129
from py42.sdk.file_event_query.event_query import Source
@@ -22,6 +19,7 @@
2219
from code42cli.securitydata.logger_factory import get_error_logger
2320
from code42cli.securitydata.options import ExposureType as ExposureTypeOptions
2421
from code42cli.util import print_error, print_bold, is_interactive
22+
from code42cli.sdk_client import create_sdk
2523

2624
_EXCEPTIONS_OCCURRED = False
2725

@@ -41,7 +39,7 @@ def extract(output_logger, args):
4139
store = _create_cursor_store(args, profile)
4240
filters = _get_filters(args, store)
4341
handlers = _create_event_handlers(output_logger, store)
44-
sdk = _get_sdk(profile, args.is_debug_mode)
42+
sdk = create_sdk(profile, args.is_debug_mode)
4543
extractor = FileEventExtractor(sdk, handlers)
4644
_call_extract(extractor, filters, args)
4745
_handle_result()
@@ -85,12 +83,12 @@ def _verify_begin_date_requirements(args, cursor_store):
8583
def _begin_date_is_required(args, cursor_store):
8684
if not args.is_incremental:
8785
return True
88-
required = cursor_store is not None and cursor_store.get_stored_insertion_timestamp() is None
86+
is_required = cursor_store and cursor_store.get_stored_insertion_timestamp() is None
8987

9088
# Ignore begin date when is incremental mode, it is not required, and it was passed an argument.
91-
if not required and args.begin_date:
89+
if not is_required and args.begin_date:
9290
args.begin_date = None
93-
return required
91+
return is_required
9492

9593

9694
def _verify_exposure_types(exposure_types):
@@ -122,7 +120,9 @@ def _create_filters(args):
122120

123121
def _get_event_timestamp_filter(args):
124122
try:
125-
return date_helper.create_event_timestamp_filter(args.begin_date, args.end_date)
123+
begin_date = args.begin_date.strip().split(" ") if args.begin_date else None
124+
end_date = args.end_date.strip().split(" ") if args.end_date else None
125+
return date_helper.create_event_timestamp_filter(begin_date, end_date)
126126
except ValueError as ex:
127127
print_error(str(ex))
128128
exit(1)
@@ -153,20 +153,6 @@ def handle_response(response):
153153
return handlers
154154

155155

156-
def _get_sdk(profile, is_debug_mode):
157-
if is_debug_mode:
158-
settings.debug_level = debug_level.DEBUG
159-
try:
160-
password = profile.get_password()
161-
return SDK.create_using_local_account(profile.authority_url, profile.username, password)
162-
except Exception:
163-
print_error(
164-
u"Invalid credentials or host address. "
165-
u"Verify your profile is set up correctly and that you are supplying the correct password."
166-
)
167-
exit(1)
168-
169-
170156
def _call_extract(extractor, filters, args):
171157
if args.advanced_query:
172158
extractor.extract_advanced(args.advanced_query)

src/code42cli/securitydata/logger_factory.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ def get_logger_for_file(filename, output_format):
4747

4848
with _logger_deps_lock:
4949
if not _logger_has_handlers(logger):
50-
handler = logging.FileHandler(filename, delay=True)
50+
handler = logging.FileHandler(filename, delay=True, encoding="utf-8")
5151
return _init_logger(logger, handler, output_format)
5252
return logger
5353

@@ -86,7 +86,7 @@ def get_error_logger():
8686
with _logger_deps_lock:
8787
if not _logger_has_handlers(logger):
8888
formatter = logging.Formatter(u"%(asctime)s %(message)s")
89-
handler = RotatingFileHandler(log_path, maxBytes=250000000)
89+
handler = RotatingFileHandler(log_path, maxBytes=250000000, encoding="utf-8")
9090
return _apply_logger_dependencies(logger, handler, formatter)
9191
return logger
9292

src/code42cli/util.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,13 @@ def get_user_project_path(subdir=""):
2727

2828
def open_file(file_path, mode, action):
2929
"""Wrapper for opening files, useful for testing purposes."""
30-
with open(file_path, mode) as f:
30+
with open(file_path, mode, encoding="utf-8") as f:
3131
action(f)
3232

3333

3434
def print_error(error_text):
3535
"""Prints red text."""
36-
print("\033[91mUSAGE ERROR: {}\033[0m".format(error_text))
36+
print("\033[91mERROR: {}\033[0m".format(error_text))
3737

3838

3939
def print_bold(bold_text):

tests/conftest.py

+43
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44

55
import pytest
66

7+
from code42cli.profile.config import ConfigAccessor
8+
from code42cli.profile.profile import Code42Profile
9+
710

811
@pytest.fixture
912
def namespace(mocker):
@@ -28,6 +31,46 @@ def namespace(mocker):
2831
return mock
2932

3033

34+
def create_profile_values_dict(authority=None, username=None, ignore_ssl=False):
35+
return {
36+
ConfigAccessor.AUTHORITY_KEY: authority,
37+
ConfigAccessor.USERNAME_KEY: username,
38+
ConfigAccessor.IGNORE_SSL_ERRORS_KEY: ignore_ssl,
39+
}
40+
41+
42+
class MockSection(object):
43+
def __init__(self, name="Test Profile Name", values_dict=None):
44+
self.name = name
45+
self.values_dict = values_dict or create_profile_values_dict()
46+
47+
def __getitem__(self, item):
48+
return self.values_dict[item]
49+
50+
def __setitem__(self, key, value):
51+
self.values_dict[key] = value
52+
53+
def get(self, item):
54+
return self.values_dict.get(item)
55+
56+
57+
def create_mock_profile(name=None):
58+
profile_section = MockSection(name)
59+
profile = Code42Profile(profile_section)
60+
61+
def mock_get_password():
62+
return "Test Password"
63+
64+
profile.get_password = mock_get_password
65+
return profile
66+
67+
68+
def setup_mock_accessor(mock_accessor, name=None, values_dict=None):
69+
profile_section = MockSection(name, values_dict)
70+
mock_accessor.get_profile.return_value = profile_section
71+
return mock_accessor
72+
73+
3174
def get_filter_value_from_json(json, filter_index):
3275
return json_module.loads(str(json))["filters"][filter_index]["value"]
3376

0 commit comments

Comments
 (0)