Skip to content

Commit c5e7877

Browse files
author
Juliya Smith
authored
Feature/headless pw mgmt (#17)
1 parent 2217fbb commit c5e7877

File tree

17 files changed

+286
-64
lines changed

17 files changed

+286
-64
lines changed

CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,15 @@ 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.3 - 2020-03-16
12+
13+
### Added
14+
15+
- Support for storing passwords when keying is not available.
16+
17+
### Fixed
18+
19+
- Bug where keyring caused errors on certain operating systems when not supported.
1120

1221
## 0.4.2 - 2020-03-13
1322

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,3 +130,9 @@ To learn more about acceptable arguments, add the `-h` flag to `code42` or any o
130130
# Known Issues
131131

132132
Only the first 10,000 of each set of events containing the exact same insertion timestamp is reported.
133+
134+
135+
# Troubleshooting
136+
137+
If you keep getting prompted for your password, try resetting with `code42 profile reset-pw`.
138+
If that doesn't work, delete your credentials file located at ~/.code42cli or the entry in keychain.

src/code42cli/__version__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = "0.4.2"
1+
__version__ = "0.4.3"

src/code42cli/compat.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@
1515
from urlparse import urljoin, urlparse
1616

1717
str = unicode
18+
1819
import io
20+
1921
open = io.open
2022

2123
import repr as reprlib

src/code42cli/profile/config.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ def _create_profile_section(self, name):
115115
self._internal[self.DEFAULT_PROFILE] = name
116116

117117
def _save(self):
118-
util.open_file(self.path, u"w+", lambda f: self.parser.write(f))
118+
util.open_file(self.path, u"w+", lambda file: self.parser.write(file))
119119

120120
def _try_complete_setup(self, profile):
121121
if self._internal.getboolean(self.DEFAULT_PROFILE_IS_COMPLETE):

src/code42cli/profile/password.py

Lines changed: 82 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,21 @@
11
from __future__ import print_function
22

3+
import os
4+
import stat
35
from getpass import getpass
46

57
import keyring
68

79
from code42cli.profile.config import get_config_accessor, ConfigAccessor
10+
from code42cli.util import does_user_agree, open_file, get_user_project_path, print_error
811

912
_ROOT_SERVICE_NAME = u"code42cli"
1013

1114

1215
def get_stored_password(profile_name):
1316
"""Gets your currently stored password for the given profile name."""
1417
profile = _get_profile(profile_name)
15-
service_name = _get_service_name(profile.name)
16-
username = _get_username(profile)
17-
password = keyring.get_password(service_name, username)
18-
return password
18+
return _get_stored_password(profile)
1919

2020

2121
def get_password_from_prompt():
@@ -26,20 +26,94 @@ def get_password_from_prompt():
2626
def set_password(profile_name, new_password):
2727
"""Sets your password for the given profile name."""
2828
profile = _get_profile(profile_name)
29-
service_name = _get_service_name(profile.name)
29+
service_name = _get_keyring_service_name(profile.name)
3030
username = _get_username(profile)
31-
keyring.set_password(service_name, username, new_password)
32-
print(u"'Code42 Password' updated.")
31+
if _store_password(profile, service_name, username, new_password):
32+
print(u"'Code42 Password' updated.")
3333

3434

3535
def _get_profile(profile_name):
3636
accessor = get_config_accessor()
3737
return accessor.get_profile(profile_name)
3838

3939

40-
def _get_service_name(profile_name):
40+
def _get_stored_password(profile):
41+
password = _get_password_from_keyring(profile) or _get_password_from_file(profile)
42+
return password
43+
44+
45+
def _get_keyring_service_name(profile_name):
4146
return u"{}::{}".format(_ROOT_SERVICE_NAME, profile_name)
4247

4348

49+
def _get_password_from_keyring(profile):
50+
try:
51+
service_name = _get_keyring_service_name(profile.name)
52+
username = _get_username(profile)
53+
return keyring.get_password(service_name, username)
54+
except:
55+
return None
56+
57+
58+
def _get_password_from_file(profile):
59+
path = _get_password_file_path(profile)
60+
61+
def read_password(file):
62+
try:
63+
return file.readline().strip()
64+
except Exception:
65+
return None
66+
67+
try:
68+
return open_file(path, u"r", lambda file: read_password(file))
69+
except Exception:
70+
return None
71+
72+
73+
def _store_password(profile, service_name, username, new_password):
74+
return _store_password_using_keyring(
75+
service_name, username, new_password
76+
) or _store_password_using_file(profile, new_password)
77+
78+
79+
def _store_password_using_keyring(service_name, username, new_password):
80+
try:
81+
keyring.set_password(service_name, username, new_password)
82+
was_successful = keyring.get_password(service_name, username) is not None
83+
return was_successful
84+
except:
85+
return False
86+
87+
88+
def _store_password_using_file(profile, new_password):
89+
save_to_file = _prompt_for_alternative_store()
90+
if save_to_file:
91+
path = _get_password_file_path(profile)
92+
93+
def write_password(file):
94+
try:
95+
file.truncate(0)
96+
line = u"{0}\n".format(new_password)
97+
file.write(line)
98+
os.chmod(path, stat.S_IRUSR | stat.S_IWUSR)
99+
return True
100+
except Exception as ex:
101+
print_error(str(ex))
102+
return False
103+
104+
return open_file(path, u"w+", lambda file: write_password(file))
105+
return False
106+
107+
108+
def _get_password_file_path(profile):
109+
project_path = get_user_project_path()
110+
return u"{0}.{1}".format(project_path, profile.name.lower())
111+
112+
44113
def _get_username(profile):
45114
return profile[ConfigAccessor.USERNAME_KEY]
115+
116+
117+
def _prompt_for_alternative_store():
118+
prompt = u"keyring is unavailable. Would you like to store in secure flat file? (y/n): "
119+
return does_user_agree(prompt)

src/code42cli/profile/profile.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,13 @@
44
import code42cli.profile.password as password
55
from code42cli.compat import str
66
from code42cli.profile.config import get_config_accessor, ConfigAccessor
7+
from code42cli.sdk_client import validate_connection
78
from code42cli.util import (
8-
get_input,
9+
does_user_agree,
910
print_error,
1011
print_set_profile_help,
1112
print_no_existing_profile_message,
1213
)
13-
from code42cli.sdk_client import validate_connection
1414

1515

1616
class Code42Profile(object):
@@ -112,6 +112,7 @@ def prompt_for_password_reset(args):
112112
"""Securely prompts for your password and then stores it using keyring."""
113113
profile = get_profile(args.profile_name)
114114
new_password = password.get_password_from_prompt()
115+
115116
if not validate_connection(profile.authority_url, profile.username, new_password):
116117
print_error(
117118
"Your password was not saved because your credentials failed to validate. "
@@ -254,8 +255,7 @@ def _default_profile_exist():
254255

255256

256257
def _prompt_for_allow_password_set(args):
257-
answer = get_input(u"Would you like to set a password? (y/n): ")
258-
if answer.lower() == u"y":
258+
if does_user_agree(u"Would you like to set a password? (y/n): "):
259259
prompt_for_password_reset(args)
260260

261261

src/code42cli/sdk_client.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,5 +24,4 @@ def validate_connection(authority_url, username, password):
2424
SDK.create_using_local_account(authority_url, username, password)
2525
return True
2626
except:
27-
print(username, password, authority_url)
2827
return False

src/code42cli/securitydata/extraction.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,14 @@
1212

1313
from code42cli.compat import str
1414
from code42cli.profile.profile import get_profile
15+
from code42cli.sdk_client import create_sdk
1516
from code42cli.securitydata import date_helper as date_helper
1617
from code42cli.securitydata.arguments.main import IS_INCREMENTAL_KEY
1718
from code42cli.securitydata.arguments.search import SearchArguments
1819
from code42cli.securitydata.cursor_store import FileEventCursorStore
1920
from code42cli.securitydata.logger_factory import get_error_logger
2021
from code42cli.securitydata.options import ExposureType as ExposureTypeOptions
2122
from code42cli.util import print_error, print_bold, is_interactive
22-
from code42cli.sdk_client import create_sdk
2323

2424
_EXCEPTIONS_OCCURRED = False
2525

src/code42cli/securitydata/logger_factory.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -68,9 +68,13 @@ def get_logger_for_server(hostname, protocol, output_format):
6868
if not _logger_has_handlers(logger):
6969
url_parts = get_url_parts(hostname)
7070
port = url_parts[1] or 514
71-
handler = NoPrioritySysLogHandlerWrapper(
72-
url_parts[0], port=port, protocol=protocol
73-
).handler
71+
try:
72+
handler = NoPrioritySysLogHandlerWrapper(
73+
url_parts[0], port=port, protocol=protocol
74+
).handler
75+
except:
76+
print_error(u"Unable to connect to {0}.".format(hostname))
77+
exit(1)
7478
return _init_logger(logger, handler, output_format)
7579
return logger
7680

0 commit comments

Comments
 (0)