Skip to content

Commit eb913ca

Browse files
authored
Have profile commands use create_sdk so disabling ssl verification works (#288)
* have profile commands use create_sdk so disabling ssl verification works * fix totp and tests * make validate_connection internal, test create_sdk instead * CHANGELOG entry * rm PRODUCT_NAME var * fix test names
1 parent 8fb7370 commit eb913ca

File tree

6 files changed

+70
-62
lines changed

6 files changed

+70
-62
lines changed

CHANGELOG.md

+6
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,12 @@ 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+
## Unreleased
12+
13+
### Fixed
14+
15+
- Issue where `profile` commands that required connecting to an authority failed to respect the `--disable-ssl-errors` flag when set.
16+
1117
## 1.6.0 - 2021-05-20
1218

1319
### Added

src/code42cli/cmds/profile.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
from code42cli.errors import Code42CLIError
1010
from code42cli.options import yes_option
1111
from code42cli.profile import CREATE_PROFILE_HELP
12-
from code42cli.sdk_client import validate_connection
12+
from code42cli.sdk_client import create_sdk
1313
from code42cli.util import does_user_agree
1414

1515

@@ -193,7 +193,7 @@ def _prompt_for_allow_password_set(profile_name):
193193
def _set_pw(profile_name, password):
194194
c42profile = cliprofile.get_profile(profile_name)
195195
try:
196-
validate_connection(c42profile.authority_url, c42profile.username, password)
196+
create_sdk(c42profile, is_debug_mode=False, password=password)
197197
except Py42MFARequiredError:
198198
echo(
199199
"Multi-factor account detected. `--totp <token>` option will be required for all code42 invocations."

src/code42cli/options.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ def profile(self, value):
6060
@property
6161
def sdk(self):
6262
if self._sdk is None:
63-
self._sdk = create_sdk(self.profile, self.debug, self.totp)
63+
self._sdk = create_sdk(self.profile, self.debug, totp=self.totp)
6464
return self._sdk
6565

6666
def set_assume_yes(self, param):

src/code42cli/sdk_client.py

+14-8
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from py42.exceptions import Py42MFARequiredError
88
from py42.exceptions import Py42UnauthorizedError
99
from requests.exceptions import ConnectionError
10+
from requests.exceptions import SSLError
1011

1112
from code42cli.errors import Code42CLIError
1213
from code42cli.errors import LoggedCLIError
@@ -17,7 +18,7 @@
1718
logger = get_main_cli_logger()
1819

1920

20-
def create_sdk(profile, is_debug_mode, totp=None):
21+
def create_sdk(profile, is_debug_mode, password=None, totp=None):
2122
if is_debug_mode:
2223
py42.settings.debug.level = debug.DEBUG
2324
if profile.ignore_ssl_errors == "True":
@@ -31,25 +32,30 @@ def create_sdk(profile, is_debug_mode, totp=None):
3132
requests.packages.urllib3.exceptions.InsecureRequestWarning
3233
)
3334
py42.settings.verify_ssl_certs = False
34-
password = profile.get_password()
35-
return validate_connection(profile.authority_url, profile.username, password, totp)
35+
password = password or profile.get_password()
36+
return _validate_connection(profile.authority_url, profile.username, password, totp)
3637

3738

38-
def validate_connection(authority_url, username, password, totp=None):
39+
def _validate_connection(authority_url, username, password, totp=None):
3940
try:
4041
return py42.sdk.from_local_account(authority_url, username, password, totp=totp)
42+
except SSLError as err:
43+
logger.log_error(err)
44+
raise LoggedCLIError(
45+
f"Problem connecting to {authority_url}, SSL certificate verification failed.\nUpdate profile with --disable-ssl-errors to bypass certificate checks (not recommended!)."
46+
)
4147
except ConnectionError as err:
42-
logger.log_error(str(err))
48+
logger.log_error(err)
4349
raise LoggedCLIError(f"Problem connecting to {authority_url}.")
4450
except Py42MFARequiredError:
4551
totp = prompt("Multi-factor authentication required. Enter TOTP", type=int)
46-
return validate_connection(authority_url, username, password, totp)
52+
return _validate_connection(authority_url, username, password, totp)
4753
except Py42UnauthorizedError as err:
48-
logger.log_error(str(err))
54+
logger.log_error(err)
4955
if "INVALID_TIME_BASED_ONE_TIME_PASSWORD" in err.response.text:
5056
raise Code42CLIError(f"Invalid TOTP token for user {username}.")
5157
else:
5258
raise Code42CLIError(f"Invalid credentials for user {username}.")
5359
except Exception as err:
54-
logger.log_error(str(err))
60+
logger.log_error(err)
5561
raise LoggedCLIError("Unknown problem validating connection.")

tests/cmds/test_profile.py

+9-8
Original file line numberDiff line numberDiff line change
@@ -1,48 +1,49 @@
11
import pytest
22
from py42.exceptions import Py42MFARequiredError
3+
from py42.sdk import SDKClient
34
from requests import Response
45
from requests.exceptions import HTTPError
56

67
from ..conftest import create_mock_profile
7-
from code42cli import PRODUCT_NAME
88
from code42cli.errors import Code42CLIError
99
from code42cli.errors import LoggedCLIError
1010
from code42cli.main import cli
1111

1212

1313
@pytest.fixture
1414
def user_agreement(mocker):
15-
mock = mocker.patch("{}.cmds.profile.does_user_agree".format(PRODUCT_NAME))
15+
mock = mocker.patch("code42cli.cmds.profile.does_user_agree")
1616
mock.return_value = True
1717
return mocker
1818

1919

2020
@pytest.fixture
2121
def user_disagreement(mocker):
22-
mock = mocker.patch("{}.cmds.profile.does_user_agree".format(PRODUCT_NAME))
22+
mock = mocker.patch("code42cli.cmds.profile.does_user_agree")
2323
mock.return_value = False
2424
return mocker
2525

2626

2727
@pytest.fixture
2828
def mock_cliprofile_namespace(mocker):
29-
return mocker.patch("{}.cmds.profile.cliprofile".format(PRODUCT_NAME))
29+
return mocker.patch("code42cli.cmds.profile.cliprofile")
3030

3131

3232
@pytest.fixture(autouse=True)
3333
def mock_getpass(mocker):
34-
mock = mocker.patch("{}.cmds.profile.getpass".format(PRODUCT_NAME))
34+
mock = mocker.patch("code42cli.cmds.profile.getpass")
3535
mock.return_value = "newpassword"
3636

3737

3838
@pytest.fixture
3939
def mock_verify(mocker):
40-
return mocker.patch("{}.cmds.profile.validate_connection".format(PRODUCT_NAME))
40+
return mocker.patch("code42cli.cmds.profile.create_sdk")
4141

4242

4343
@pytest.fixture
44-
def valid_connection(mock_verify):
45-
mock_verify.return_value = True
44+
def valid_connection(mocker, mock_verify):
45+
mock_sdk = mocker.MagicMock(spec=SDKClient)
46+
mock_verify.return_value = mock_sdk
4647
return mock_verify
4748

4849

tests/test_sdk_client.py

+38-43
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
from code42cli.main import cli
1717
from code42cli.options import CLIState
1818
from code42cli.sdk_client import create_sdk
19-
from code42cli.sdk_client import validate_connection
2019

2120

2221
@pytest.fixture
@@ -29,6 +28,17 @@ def mock_sdk_factory(mocker):
2928
return mocker.patch("py42.sdk.from_local_account")
3029

3130

31+
@pytest.fixture
32+
def mock_profile_with_password():
33+
profile = create_mock_profile()
34+
35+
def mock_get_password():
36+
return "Test Password"
37+
38+
profile.get_password = mock_get_password
39+
return profile
40+
41+
3242
@pytest.fixture
3343
def requests_exception(mocker):
3444
mock_response = mocker.MagicMock(spec=Response)
@@ -54,99 +64,84 @@ def test_create_sdk_when_profile_has_ssl_errors_disabled_sets_py42_setting_and_p
5464

5565

5666
def test_create_sdk_when_py42_exception_occurs_raises_and_logs_cli_error(
57-
sdk_logger, mock_sdk_factory, requests_exception
67+
sdk_logger, mock_sdk_factory, requests_exception, mock_profile_with_password
5868
):
5969

6070
mock_sdk_factory.side_effect = Py42UnauthorizedError(requests_exception)
61-
profile = create_mock_profile()
62-
63-
def mock_get_password():
64-
return "Test Password"
6571

66-
profile.get_password = mock_get_password
6772
with pytest.raises(Code42CLIError) as err:
68-
create_sdk(profile, False)
73+
create_sdk(mock_profile_with_password, False)
6974

7075
assert "Invalid credentials for user" in err.value.message
7176
assert sdk_logger.log_error.call_count == 1
72-
assert "Failure in HTTP call" in sdk_logger.log_error.call_args[0][0]
77+
assert "Failure in HTTP call" in str(sdk_logger.log_error.call_args[0][0])
7378

7479

7580
def test_create_sdk_when_connection_exception_occurs_raises_and_logs_cli_error(
76-
sdk_logger, mock_sdk_factory
81+
sdk_logger, mock_sdk_factory, mock_profile_with_password
7782
):
7883
mock_sdk_factory.side_effect = ConnectionError("connection message")
79-
profile = create_mock_profile()
8084

81-
def mock_get_password():
82-
return "Test Password"
83-
84-
profile.get_password = mock_get_password
8585
with pytest.raises(LoggedCLIError) as err:
86-
create_sdk(profile, False)
86+
create_sdk(mock_profile_with_password, False)
8787

8888
assert "Problem connecting to" in err.value.message
8989
assert sdk_logger.log_error.call_count == 1
90-
assert "connection message" in sdk_logger.log_error.call_args[0][0]
90+
assert "connection message" in str(sdk_logger.log_error.call_args[0][0])
9191

9292

9393
def test_create_sdk_when_unknown_exception_occurs_raises_and_logs_cli_error(
94-
sdk_logger, mock_sdk_factory
94+
sdk_logger, mock_sdk_factory, mock_profile_with_password
9595
):
9696
mock_sdk_factory.side_effect = Exception("test message")
97-
profile = create_mock_profile()
98-
99-
def mock_get_password():
100-
return "Test Password"
10197

102-
profile.get_password = mock_get_password
10398
with pytest.raises(LoggedCLIError) as err:
104-
create_sdk(profile, False)
99+
create_sdk(mock_profile_with_password, False)
105100

106101
assert "Unknown problem validating" in err.value.message
107102
assert sdk_logger.log_error.call_count == 1
108-
assert "test message" in sdk_logger.log_error.call_args[0][0]
109-
103+
assert "test message" in str(sdk_logger.log_error.call_args[0][0])
110104

111-
def test_create_sdk_when_told_to_debug_turns_on_debug(mock_sdk_factory):
112-
profile = create_mock_profile()
113-
114-
def mock_get_password():
115-
return "Test Password"
116105

117-
profile.get_password = mock_get_password
118-
create_sdk(profile, True)
106+
def test_create_sdk_when_told_to_debug_turns_on_debug(
107+
mock_sdk_factory, mock_profile_with_password
108+
):
109+
create_sdk(mock_profile_with_password, True)
119110
assert py42.settings.debug.level == debug.DEBUG
120111

121112

122-
def test_validate_connection_uses_given_credentials(mock_sdk_factory):
123-
assert validate_connection("Authority", "Test", "Password", None)
124-
mock_sdk_factory.assert_called_once_with("Authority", "Test", "Password", totp=None)
113+
def test_create_sdk_uses_given_credentials(
114+
mock_sdk_factory, mock_profile_with_password
115+
):
116+
create_sdk(mock_profile_with_password, False)
117+
mock_sdk_factory.assert_called_once_with(
118+
"example.com", "foo", "Test Password", totp=None
119+
)
125120

126121

127-
def test_validate_connection_when_mfa_required_exception_raised_prompts_for_totp(
128-
mocker, monkeypatch, mock_sdk_factory, capsys
122+
def test_create_sdk_connection_when_mfa_required_exception_raised_prompts_for_totp(
123+
mocker, monkeypatch, mock_sdk_factory, capsys, mock_profile_with_password
129124
):
130125
monkeypatch.setattr("sys.stdin", StringIO("101010"))
131126
response = mocker.MagicMock(spec=Response)
132127
mock_sdk_factory.side_effect = [
133128
Py42MFARequiredError(HTTPError(response=response)),
134129
None,
135130
]
136-
validate_connection("Authority", "Test", "Password", None)
131+
create_sdk(mock_profile_with_password, False)
137132
output = capsys.readouterr()
138133
assert "Multi-factor authentication required. Enter TOTP:" in output.out
139134

140135

141-
def test_validate_connection_when_mfa_token_invalid_raises_expected_cli_error(
142-
mocker, mock_sdk_factory
136+
def test_create_sdk_connection_when_mfa_token_invalid_raises_expected_cli_error(
137+
mocker, mock_sdk_factory, mock_profile_with_password
143138
):
144139
response = mocker.MagicMock(spec=Response)
145140
response.text = '{"data":null,"error":[{"primaryErrorKey":"INVALID_TIME_BASED_ONE_TIME_PASSWORD","otherErrors":null}],"warnings":null}'
146141
mock_sdk_factory.side_effect = Py42UnauthorizedError(HTTPError(response=response))
147142
with pytest.raises(Code42CLIError) as err:
148-
validate_connection("Authority", "Test", "Password", "1234")
149-
assert str(err.value) == "Invalid TOTP token for user Test."
143+
create_sdk(mock_profile_with_password, False, totp="1234")
144+
assert str(err.value) == "Invalid TOTP token for user foo."
150145

151146

152147
def test_totp_option_when_passed_is_passed_to_sdk_initialization(

0 commit comments

Comments
 (0)