Skip to content

Commit 70696c7

Browse files
authored
feat: introduce 4 new methods for the CredentialManager and deprecate some old ones (#189)
* feat: introduce 4 new methods for the CredentialManager and deprecate some old ones This commit introduces 4 new methods for the CredentialManager: get_raw_passwords, get_raw_passwords_in_realm, get_clear_passwords, get_clear_passwords_in_realm which now should be used to access passwords through CredentialManager. Some old methods like _get_all_passwords_in_realm and _get_all_passwords were deprecated. * chore: pre-commit autoupdate
1 parent 75f9d08 commit 70696c7

File tree

6 files changed

+247
-384
lines changed

6 files changed

+247
-384
lines changed

.pre-commit-config.yaml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,24 @@
11
repos:
22
- repo: https://github.com/asottile/pyupgrade
3-
rev: v2.31.1
3+
rev: v2.37.3
44
hooks:
55
- id: pyupgrade
66
args: [--py37-plus]
77
- repo: https://github.com/psf/black
8-
rev: 22.3.0
8+
rev: 22.6.0
99
hooks:
1010
- id: black
1111
- repo: https://github.com/PyCQA/isort
1212
rev: 5.10.1
1313
hooks:
1414
- id: isort
1515
- repo: https://github.com/PyCQA/flake8
16-
rev: 4.0.1
16+
rev: 5.0.4
1717
hooks:
1818
- id: flake8
1919
exclude: ^tests
2020
- repo: https://github.com/myint/docformatter
21-
rev: v1.4
21+
rev: v1.5.0
2222
hooks:
2323
- id: docformatter
2424
args: [--in-place]

solnlib/credentials.py

Lines changed: 73 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,10 @@
1818

1919
import json
2020
import re
21+
import warnings
22+
from typing import Dict, List
2123

22-
from splunklib import binding
24+
from splunklib import binding, client
2325

2426
from . import splunk_rest_client as rest_client
2527
from .net_utils import validate_scheme_host_port
@@ -79,7 +81,7 @@ def __init__(
7981
port: int = None,
8082
**context: dict,
8183
):
82-
"""Initializes CredentialsManager.
84+
"""Initializes CredentialManager.
8385
8486
Arguments:
8587
session_key: Splunk access token.
@@ -123,9 +125,11 @@ def get_password(self, user: str) -> str:
123125
realm='realm_test')
124126
>>> cm.get_password('testuser2')
125127
"""
126-
127-
all_passwords = self._get_all_passwords()
128-
for password in all_passwords:
128+
if self._realm is not None:
129+
passwords = self.get_clear_passwords_in_realm()
130+
else:
131+
passwords = self.get_clear_passwords()
132+
for password in passwords:
129133
if password["username"] == user and password["realm"] == self._realm:
130134
return password["clear_password"]
131135

@@ -182,14 +186,16 @@ def _update_password(self, user: str, password: str):
182186
self._storage_passwords.create(password, user, self._realm)
183187
except binding.HTTPError as ex:
184188
if ex.status == 409:
185-
all_passwords = self._get_all_passwords_in_realm()
186-
for pwd_stanza in all_passwords:
189+
if self._realm is not None:
190+
passwords = self.get_raw_passwords_in_realm()
191+
else:
192+
passwords = self.get_raw_passwords()
193+
for pwd_stanza in passwords:
187194
if pwd_stanza.realm == self._realm and pwd_stanza.username == user:
188195
pwd_stanza.update(password=password)
189196
return
190197
raise ValueError(
191-
"Can not get the password object for realm: %s user: %s"
192-
% (self._realm, user)
198+
f"Can not get the password object for realm: {self._realm} user: {user}"
193199
)
194200
else:
195201
raise ex
@@ -211,25 +217,61 @@ def delete_password(self, user: str):
211217
realm='realm_test')
212218
>>> cm.delete_password('testuser1')
213219
"""
214-
all_passwords = self._get_all_passwords_in_realm()
220+
if self._realm is not None:
221+
passwords = self.get_raw_passwords_in_realm()
222+
else:
223+
passwords = self.get_raw_passwords()
215224
deleted = False
216225
ent_pattern = re.compile(
217226
r"({}{}\d+)".format(user.replace("\\", "\\\\"), self.SEP)
218227
)
219-
for password in list(all_passwords):
228+
for password in passwords:
220229
match = (user == password.username) or ent_pattern.match(password.username)
221230
if match and password.realm == self._realm:
222231
password.delete()
223232
deleted = True
224233

225234
if not deleted:
226235
raise CredentialNotExistException(
227-
"Failed to delete password of realm={}, user={}".format(
228-
self._realm, user
229-
)
236+
f"Failed to delete password of realm={self._realm}, user={user}"
230237
)
231238

232-
def _get_all_passwords_in_realm(self):
239+
def get_raw_passwords(self) -> List[client.StoragePassword]:
240+
"""Returns all passwords in the "raw" format."""
241+
warnings.warn(
242+
"Please pass realm to the CredentialManager, "
243+
"so it can utilize get_raw_passwords_in_realm method instead."
244+
)
245+
return self._storage_passwords.list(count=-1)
246+
247+
def get_raw_passwords_in_realm(self) -> List[client.StoragePassword]:
248+
"""Returns all passwords within the realm in the "raw" format."""
249+
if self._realm is None:
250+
raise ValueError("No realm was specified")
251+
return self._storage_passwords.list(count=-1, search=f"realm={self._realm}")
252+
253+
def get_clear_passwords(self) -> List[Dict[str, str]]:
254+
"""Returns all passwords in the "clear" format."""
255+
warnings.warn(
256+
"Please pass realm to the CredentialManager, "
257+
"so it can utilize get_clear_passwords_in_realm method instead."
258+
)
259+
raw_passwords = self.get_raw_passwords()
260+
return self._get_clear_passwords(raw_passwords)
261+
262+
def get_clear_passwords_in_realm(self) -> List[Dict[str, str]]:
263+
"""Returns all passwords within the realm in the "clear" format."""
264+
if self._realm is None:
265+
raise ValueError("No realm was specified")
266+
raw_passwords = self.get_raw_passwords_in_realm()
267+
return self._get_clear_passwords(raw_passwords)
268+
269+
def _get_all_passwords_in_realm(self) -> List[client.StoragePassword]:
270+
warnings.warn(
271+
"_get_all_passwords_in_realm is deprecated, "
272+
"please use get_raw_passwords_in_realm instead.",
273+
stacklevel=2,
274+
)
233275
if self._realm:
234276
all_passwords = self._storage_passwords.list(
235277
count=-1, search=f"realm={self._realm}"
@@ -238,13 +280,12 @@ def _get_all_passwords_in_realm(self):
238280
all_passwords = self._storage_passwords.list(count=-1, search="")
239281
return all_passwords
240282

241-
@retry(exceptions=[binding.HTTPError])
242-
def _get_all_passwords(self):
243-
all_passwords = self._storage_passwords.list(count=-1)
244-
283+
def _get_clear_passwords(
284+
self, passwords: List[client.StoragePassword]
285+
) -> List[Dict[str, str]]:
245286
results = {}
246287
ptn = re.compile(rf"(.+){self.SEP}(\d+)")
247-
for password in all_passwords:
288+
for password in passwords:
248289
match = ptn.match(password.name)
249290
if match:
250291
actual_name = match.group(1) + ":"
@@ -263,7 +304,7 @@ def _get_all_passwords(self):
263304

264305
# Backward compatibility
265306
# To deal with the password with only one stanza which is generated by the old version.
266-
for password in all_passwords:
307+
for password in passwords:
267308
match = ptn.match(password.name)
268309
if (not match) and (password.name not in results):
269310
results[password.name] = {
@@ -289,6 +330,16 @@ def _get_all_passwords(self):
289330

290331
return list(results.values())
291332

333+
@retry(exceptions=[binding.HTTPError])
334+
def _get_all_passwords(self) -> List[Dict[str, str]]:
335+
warnings.warn(
336+
"_get_all_passwords is deprecated, "
337+
"please use get_all_passwords_in_realm instead.",
338+
stacklevel=2,
339+
)
340+
passwords = self._storage_passwords.list(count=-1)
341+
return self._get_clear_passwords(passwords)
342+
292343

293344
@retry(exceptions=[binding.HTTPError])
294345
def get_session_key(
@@ -317,7 +368,7 @@ def get_session_key(
317368
ValueError: if scheme, host or port are invalid.
318369
319370
Examples:
320-
>>> credentials.get_session_key('user', 'password')
371+
>>> get_session_key('user', 'password')
321372
"""
322373
validate_scheme_host_port(scheme, host, port)
323374

tests/integration/test_conf_manager.py

Lines changed: 100 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,8 @@
2626
from solnlib import conf_manager
2727

2828

29-
def test_conf_manager():
30-
session_key = context.get_session_key()
31-
cfm = conf_manager.ConfManager(
29+
def _build_conf_manager(session_key: str) -> conf_manager.ConfManager:
30+
return conf_manager.ConfManager(
3231
session_key,
3332
context.app,
3433
owner=context.owner,
@@ -37,23 +36,106 @@ def test_conf_manager():
3736
port=context.port,
3837
)
3938

40-
try:
41-
conf = cfm.get_conf("test")
42-
except conf_manager.ConfManagerException:
43-
conf = cfm.create_conf("test")
44-
45-
assert not conf.stanza_exist("test_stanza")
46-
conf.update("test_stanza", {"k1": 1, "k2": 2}, ["k1"])
47-
assert conf.get("test_stanza")["k1"] == 1
48-
assert int(conf.get("test_stanza")["k2"]) == 2
49-
assert conf.get("test_stanza")["eai:appName"] == "solnlib_demo"
50-
assert len(conf.get_all()) == 1
51-
conf.delete("test_stanza")
39+
40+
def test_conf_manager_when_no_conf_then_throw_exception():
41+
session_key = context.get_session_key()
42+
cfm = _build_conf_manager(session_key)
43+
44+
with pytest.raises(conf_manager.ConfManagerException):
45+
cfm.get_conf("non_existent_configuration_file")
46+
47+
48+
def test_conf_manager_when_conf_file_exists_but_no_specific_stanza_then_throw_exception():
49+
session_key = context.get_session_key()
50+
cfm = _build_conf_manager(session_key)
51+
52+
splunk_ta_addon_settings_conf_file = cfm.get_conf("splunk_ta_addon_settings")
53+
5254
with pytest.raises(conf_manager.ConfStanzaNotExistException):
53-
conf.get("test_stanza")
55+
splunk_ta_addon_settings_conf_file.get(
56+
"non_existent_stanza_under_existing_conf_file"
57+
)
58+
59+
60+
@pytest.mark.parametrize(
61+
"stanza_name,expected_result",
62+
[
63+
("logging", True),
64+
("non_existent_stanza_under_existing_conf_file", False),
65+
],
66+
)
67+
def test_conf_manager_stanza_exist(stanza_name, expected_result):
68+
session_key = context.get_session_key()
69+
cfm = _build_conf_manager(session_key)
70+
71+
splunk_ta_addon_settings_conf_file = cfm.get_conf("splunk_ta_addon_settings")
72+
73+
assert (
74+
splunk_ta_addon_settings_conf_file.stanza_exist(stanza_name) == expected_result
75+
)
76+
77+
78+
def test_conf_manager_when_conf_file_exists():
79+
session_key = context.get_session_key()
80+
cfm = _build_conf_manager(session_key)
81+
82+
splunk_ta_addon_settings_conf_file = cfm.get_conf("splunk_ta_addon_settings")
83+
84+
expected_result = {
85+
"disabled": "0",
86+
"eai:access": {
87+
"app": "solnlib_demo",
88+
"can_change_perms": "1",
89+
"can_list": "1",
90+
"can_share_app": "1",
91+
"can_share_global": "1",
92+
"can_share_user": "0",
93+
"can_write": "1",
94+
"modifiable": "1",
95+
"owner": "nobody",
96+
"perms": {"read": ["*"], "write": ["admin"]},
97+
"removable": "0",
98+
"sharing": "global",
99+
},
100+
"eai:appName": "solnlib_demo",
101+
"eai:userName": "nobody",
102+
"log_level": "DEBUG",
103+
}
104+
assert splunk_ta_addon_settings_conf_file.get("logging") == expected_result
105+
106+
107+
def test_conf_manager_delete_non_existent_stanza_then_throw_exception():
108+
session_key = context.get_session_key()
109+
cfm = _build_conf_manager(session_key)
110+
111+
splunk_ta_addon_settings_conf_file = cfm.get_conf("splunk_ta_addon_settings")
112+
54113
with pytest.raises(conf_manager.ConfStanzaNotExistException):
55-
conf.delete("test_stanza")
56-
conf.reload()
114+
splunk_ta_addon_settings_conf_file.delete(
115+
"non_existent_stanza_under_existing_conf_file"
116+
)
117+
118+
119+
def test_conf_manager_create_conf():
120+
session_key = context.get_session_key()
121+
cfm = _build_conf_manager(session_key)
122+
123+
conf_file = cfm.create_conf("conf_file_that_did_not_exist_before")
124+
conf_file.update("stanza", {"key": "value"})
125+
126+
assert conf_file.get("stanza")["key"] == "value"
127+
128+
129+
def test_conf_manager_update_conf_with_encrypted_keys():
130+
session_key = context.get_session_key()
131+
cfm = _build_conf_manager(session_key)
132+
133+
conf_file = cfm.create_conf("conf_file_with_encrypted_keys")
134+
conf_file.update(
135+
"stanza", {"key1": "value1", "key2": "value2"}, encrypt_keys=["key2"]
136+
)
137+
138+
assert conf_file.get("stanza")["key2"] == "value2"
57139

58140

59141
def test_get_log_level():

0 commit comments

Comments
 (0)