jwbmisc 0.0.4__tar.gz → 0.0.5__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. {jwbmisc-0.0.4/src/jwbmisc.egg-info → jwbmisc-0.0.5}/PKG-INFO +1 -1
  2. {jwbmisc-0.0.4 → jwbmisc-0.0.5}/src/jwbmisc/_version.py +3 -3
  3. {jwbmisc-0.0.4 → jwbmisc-0.0.5}/src/jwbmisc/keeper.py +22 -21
  4. {jwbmisc-0.0.4 → jwbmisc-0.0.5}/src/jwbmisc/passwd.py +2 -2
  5. {jwbmisc-0.0.4 → jwbmisc-0.0.5/src/jwbmisc.egg-info}/PKG-INFO +1 -1
  6. {jwbmisc-0.0.4 → jwbmisc-0.0.5}/tests/test_keeper.py +60 -68
  7. {jwbmisc-0.0.4 → jwbmisc-0.0.5}/LICENSE +0 -0
  8. {jwbmisc-0.0.4 → jwbmisc-0.0.5}/MANIFEST.in +0 -0
  9. {jwbmisc-0.0.4 → jwbmisc-0.0.5}/Makefile +0 -0
  10. {jwbmisc-0.0.4 → jwbmisc-0.0.5}/README.md +0 -0
  11. {jwbmisc-0.0.4 → jwbmisc-0.0.5}/pyproject.toml +0 -0
  12. {jwbmisc-0.0.4 → jwbmisc-0.0.5}/release.sh +0 -0
  13. {jwbmisc-0.0.4 → jwbmisc-0.0.5}/setup.cfg +0 -0
  14. {jwbmisc-0.0.4 → jwbmisc-0.0.5}/src/jwbmisc/__init__.py +0 -0
  15. {jwbmisc-0.0.4 → jwbmisc-0.0.5}/src/jwbmisc/collection.py +0 -0
  16. {jwbmisc-0.0.4 → jwbmisc-0.0.5}/src/jwbmisc/exec.py +0 -0
  17. {jwbmisc-0.0.4 → jwbmisc-0.0.5}/src/jwbmisc/fs.py +0 -0
  18. {jwbmisc-0.0.4 → jwbmisc-0.0.5}/src/jwbmisc/interactive.py +0 -0
  19. {jwbmisc-0.0.4 → jwbmisc-0.0.5}/src/jwbmisc/json.py +0 -0
  20. {jwbmisc-0.0.4 → jwbmisc-0.0.5}/src/jwbmisc/string.py +0 -0
  21. {jwbmisc-0.0.4 → jwbmisc-0.0.5}/src/jwbmisc.egg-info/SOURCES.txt +0 -0
  22. {jwbmisc-0.0.4 → jwbmisc-0.0.5}/src/jwbmisc.egg-info/dependency_links.txt +0 -0
  23. {jwbmisc-0.0.4 → jwbmisc-0.0.5}/src/jwbmisc.egg-info/requires.txt +0 -0
  24. {jwbmisc-0.0.4 → jwbmisc-0.0.5}/src/jwbmisc.egg-info/top_level.txt +0 -0
  25. {jwbmisc-0.0.4 → jwbmisc-0.0.5}/tests/conftest.py +0 -0
  26. {jwbmisc-0.0.4 → jwbmisc-0.0.5}/tests/fzf +0 -0
  27. {jwbmisc-0.0.4 → jwbmisc-0.0.5}/tests/pass +0 -0
  28. {jwbmisc-0.0.4 → jwbmisc-0.0.5}/tests/test_collection.py +0 -0
  29. {jwbmisc-0.0.4 → jwbmisc-0.0.5}/tests/test_exec.py +0 -0
  30. {jwbmisc-0.0.4 → jwbmisc-0.0.5}/tests/test_fs.py +0 -0
  31. {jwbmisc-0.0.4 → jwbmisc-0.0.5}/tests/test_interactive.py +0 -0
  32. {jwbmisc-0.0.4 → jwbmisc-0.0.5}/tests/test_json.py +0 -0
  33. {jwbmisc-0.0.4 → jwbmisc-0.0.5}/tests/test_passwd.py +0 -0
  34. {jwbmisc-0.0.4 → jwbmisc-0.0.5}/tests/test_string.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: jwbmisc
3
- Version: 0.0.4
3
+ Version: 0.0.5
4
4
  Summary: Misc util functions of jwb
5
5
  Author-email: Joachim Bargsten <jw@bargsten.org>
6
6
  License: Apache License
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
28
28
  commit_id: COMMIT_ID
29
29
  __commit_id__: COMMIT_ID
30
30
 
31
- __version__ = version = '0.0.4'
32
- __version_tuple__ = version_tuple = (0, 0, 4)
31
+ __version__ = version = '0.0.5'
32
+ __version_tuple__ = version_tuple = (0, 0, 5)
33
33
 
34
- __commit_id__ = commit_id = 'gcba41a5a3'
34
+ __commit_id__ = commit_id = 'g68c0a3571'
@@ -1,5 +1,5 @@
1
1
  import json
2
- from keepercommander import vault as keeper_vault
2
+ from keepercommander import vault
3
3
  from keepercommander import api
4
4
  from time import sleep
5
5
  from keepercommander.params import KeeperParams
@@ -13,8 +13,10 @@ from logging import getLogger
13
13
 
14
14
  logger = getLogger(__name__)
15
15
 
16
+ CONFIG_FILE = Path.home() / ".config" / "jwbmisc" / "keeper.json"
16
17
 
17
- class _MinimalKeeperUI:
18
+
19
+ class MinimalKeeperUI:
18
20
  def __init__(self):
19
21
  self.waiting_for_sso_data_key = False
20
22
 
@@ -65,19 +67,19 @@ class _MinimalKeeperUI:
65
67
  step.resume()
66
68
 
67
69
 
68
- def _extract_keeper_field(record, field: str) -> str | None:
69
- if isinstance(record, keeper_vault.TypedRecord):
70
- value = record.get_typed_field(field)
71
- if value is None:
72
- value = next((f for f in record.custom if f.label == field), None)
70
+ def extract_record_field(record, field: str) -> str | None:
71
+ if not isinstance(record, vault.TypedRecord):
72
+ raise TypeError("only TypedRecord is supported")
73
+
74
+ value = record.get_typed_field(field) or record.get_typed_field(None, field)
73
75
 
74
- if value and value.value:
75
- return value.value[0] if isinstance(value.value, list) else str(value.value)
76
+ if value and value.value:
77
+ return value.value[0] if isinstance(value.value, list) else str(value.value)
76
78
 
77
79
  return None
78
80
 
79
81
 
80
- def _perform_keeper_login(params):
82
+ def perform_login(params):
81
83
  if not params.user:
82
84
  user = os.environ.get("KEEPER_USERNAME", None)
83
85
  if user is None:
@@ -96,29 +98,28 @@ def _perform_keeper_login(params):
96
98
  params.server = server
97
99
 
98
100
  try:
99
- api.login(params, login_ui=_MinimalKeeperUI())
101
+ api.login(params, login_ui=MinimalKeeperUI())
100
102
  except KeyboardInterrupt:
101
103
  raise KeyError("\nKeeper login cancelled by user.") from None
102
104
  except Exception as e:
103
105
  raise KeyError(f"Keeper login failed: {e}") from e
104
106
 
105
107
 
106
- def get_keeper_password(record_uid: str, field_path: str) -> str:
107
- config_file = Path.home() / ".config" / "keeper" / "config.json"
108
- config_file.parent.mkdir(parents=True, exist_ok=True)
108
+ def get_password(record_uid: str, field_path: str) -> str:
109
+ CONFIG_FILE.parent.mkdir(parents=True, exist_ok=True)
109
110
 
110
- params = KeeperParams(config_filename=str(config_file))
111
+ params = KeeperParams(config_filename=str(CONFIG_FILE))
111
112
 
112
- if config_file.exists():
113
+ if CONFIG_FILE.exists():
113
114
  try:
114
- params.config = json.loads(config_file.read_text())
115
+ params.config = json.loads(CONFIG_FILE.read_text())
115
116
  loader.load_config_properties(params)
116
117
  if not params.session_token:
117
118
  raise ValueError("No session token")
118
119
  except Exception:
119
- _perform_keeper_login(params)
120
+ perform_login(params)
120
121
  else:
121
- _perform_keeper_login(params)
122
+ perform_login(params)
122
123
 
123
124
  try:
124
125
  api.sync_down(params)
@@ -126,11 +127,11 @@ def get_keeper_password(record_uid: str, field_path: str) -> str:
126
127
  raise KeyError(f"Failed to sync Keeper vault: {e}") from e
127
128
 
128
129
  try:
129
- record = keeper_vault.KeeperRecord.load(params, record_uid)
130
+ record = vault.KeeperRecord.load(params, record_uid)
130
131
  except Exception as e:
131
132
  raise KeyError(f"Record {record_uid} not found: {e}") from e
132
133
 
133
- value = _extract_keeper_field(record, field_path)
134
+ value = extract_record_field(record, field_path)
134
135
  if value is None:
135
136
  raise KeyError(f"Field '{field_path}' not found in record {record_uid}")
136
137
 
@@ -72,6 +72,6 @@ def _call_unix_pass(key, lnum=1):
72
72
 
73
73
 
74
74
  def _keeper_password(record_uid: str, field_path: str) -> str:
75
- from .keeper import get_keeper_password
75
+ from .keeper import get_password as keeper_get_password
76
76
 
77
- return get_keeper_password(record_uid, field_path)
77
+ return keeper_get_password(record_uid, field_path)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: jwbmisc
3
- Version: 0.0.4
3
+ Version: 0.0.5
4
4
  Summary: Misc util functions of jwb
5
5
  Author-email: Joachim Bargsten <jw@bargsten.org>
6
6
  License: Apache License
@@ -1,69 +1,61 @@
1
+ # pyright: basic
1
2
  import pytest
2
3
 
4
+ from keepercommander import vault, utils
3
5
  from jwbmisc.keeper import (
4
- _MinimalKeeperUI,
5
- _extract_keeper_field,
6
- _perform_keeper_login,
7
- get_keeper_password,
6
+ MinimalKeeperUI,
7
+ extract_record_field,
8
+ perform_login,
9
+ get_password,
8
10
  )
9
11
 
10
12
 
11
- class MockTypedField:
12
- def __init__(self, value):
13
- self.value = value
14
- self.label = None
15
-
16
-
17
- class MockTypedRecord:
18
- def __init__(self, fields=None, custom=None):
19
- self._fields = fields or {}
20
- self.custom = custom or []
21
-
22
- def get_typed_field(self, name):
23
- return self._fields.get(name)
13
+ def gen_record(type_name="login"):
14
+ record = vault.TypedRecord()
15
+ record.type_name = type_name
16
+ record.record_uid = utils.generate_uid()
17
+ record.record_key = utils.generate_aes_key()
18
+ record.fields.append(
19
+ vault.TypedField.new_field(
20
+ field_type="password", field_label="AWS Secret Sauce", field_value=["password123"]
21
+ )
22
+ )
23
+ return record
24
24
 
25
25
 
26
26
  class TestExtractKeeperField:
27
- def test_typed_record_with_list_value(self, mocker):
28
- mocker.patch("jwbmisc.keeper.keeper_vault.TypedRecord", MockTypedRecord)
29
- field = MockTypedField(["password123"])
30
- record = MockTypedRecord(fields={"password": field})
27
+ def test_typed_record_with_list_value(self):
28
+ record = gen_record()
31
29
 
32
- result = _extract_keeper_field(record, "password")
30
+ result = extract_record_field(record, "password")
33
31
  assert result == "password123"
34
32
 
35
- def test_typed_record_with_string_value(self, mocker):
36
- mocker.patch("jwbmisc.keeper.keeper_vault.TypedRecord", MockTypedRecord)
37
- field = MockTypedField("single_value")
38
- record = MockTypedRecord(fields={"password": field})
39
-
40
- result = _extract_keeper_field(record, "password")
41
- assert result == "single_value"
42
-
43
- def test_typed_record_field_not_found(self, mocker):
44
- mocker.patch("jwbmisc.keeper.keeper_vault.TypedRecord", MockTypedRecord)
45
- record = MockTypedRecord(fields={})
46
-
47
- result = _extract_keeper_field(record, "missing")
33
+ def test_typed_record_field_not_found(self):
34
+ record = gen_record()
35
+ record.fields = []
36
+ result = extract_record_field(record, "missing")
48
37
  assert result is None
49
38
 
50
- def test_typed_record_custom_field(self, mocker):
51
- mocker.patch("jwbmisc.keeper.keeper_vault.TypedRecord", MockTypedRecord)
52
- custom_field = MockTypedField(["custom_value"])
53
- custom_field.label = "custom_field"
54
- record = MockTypedRecord(fields={}, custom=[custom_field])
55
-
56
- result = _extract_keeper_field(record, "custom_field")
57
- assert result == "custom_value"
39
+ def test_typed_record_custom_field(self):
40
+ record = gen_record()
41
+ record.custom.append(
42
+ vault.TypedField.new_field(
43
+ field_type="passwordcustom", field_label="AWS Custom Secret Sauce", field_value=["password3"]
44
+ )
45
+ )
46
+ result = extract_record_field(record, "passwordcustom")
47
+ assert result == "password3"
48
+ result = extract_record_field(record, "AWS Custom Secret Sauce")
49
+ assert result == "password3"
58
50
 
59
51
  def test_non_typed_record_returns_none(self):
60
- result = _extract_keeper_field(object(), "any_field")
61
- assert result is None
52
+ with pytest.raises(TypeError):
53
+ extract_record_field(object(), "any_field")
62
54
 
63
55
 
64
56
  class TestMinimalKeeperUI:
65
57
  def test_on_password_raises(self):
66
- ui = _MinimalKeeperUI()
58
+ ui = MinimalKeeperUI()
67
59
  with pytest.raises(KeyError, match="Password login not supported"):
68
60
  ui.on_password(None)
69
61
 
@@ -74,7 +66,7 @@ class TestMinimalKeeperUI:
74
66
  step = mocker.MagicMock()
75
67
  step.sso_login_url = "https://sso.example.com"
76
68
 
77
- ui = _MinimalKeeperUI()
69
+ ui = MinimalKeeperUI()
78
70
  ui.on_sso_redirect(step)
79
71
 
80
72
  mock_webbrowser.open_new_tab.assert_called_once_with("https://sso.example.com")
@@ -87,7 +79,7 @@ class TestMinimalKeeperUI:
87
79
  step = mocker.MagicMock()
88
80
  step.sso_login_url = "https://sso.example.com"
89
81
 
90
- ui = _MinimalKeeperUI()
82
+ ui = MinimalKeeperUI()
91
83
  with pytest.raises(ValueError, match="No SSO token"):
92
84
  ui.on_sso_redirect(step)
93
85
 
@@ -100,7 +92,7 @@ class TestMinimalKeeperUI:
100
92
  step = mocker.MagicMock()
101
93
  step.get_channels.return_value = [mock_channel]
102
94
 
103
- ui = _MinimalKeeperUI()
95
+ ui = MinimalKeeperUI()
104
96
  ui.on_two_factor(step)
105
97
 
106
98
  step.send_code.assert_called_once_with("channel_123", "123456")
@@ -113,12 +105,12 @@ class TestMinimalKeeperUI:
113
105
  step = mocker.MagicMock()
114
106
  step.get_channels.return_value = [mock_channel]
115
107
 
116
- ui = _MinimalKeeperUI()
108
+ ui = MinimalKeeperUI()
117
109
  with pytest.raises(ValueError, match="TOTP authenticator not available"):
118
110
  ui.on_two_factor(step)
119
111
 
120
112
  def test_on_device_approval_does_not_raise(self, mocker):
121
- ui = _MinimalKeeperUI()
113
+ ui = MinimalKeeperUI()
122
114
  ui.on_device_approval(None) # Should not raise
123
115
 
124
116
  def test_on_sso_data_key_first_call(self, mocker):
@@ -127,7 +119,7 @@ class TestMinimalKeeperUI:
127
119
 
128
120
  step = mocker.MagicMock()
129
121
 
130
- ui = _MinimalKeeperUI()
122
+ ui = MinimalKeeperUI()
131
123
  assert ui.waiting_for_sso_data_key is False
132
124
 
133
125
  ui.on_sso_data_key(step)
@@ -141,7 +133,7 @@ class TestMinimalKeeperUI:
141
133
  mock_sleep = mocker.patch("jwbmisc.keeper.sleep")
142
134
  step = mocker.MagicMock()
143
135
 
144
- ui = _MinimalKeeperUI()
136
+ ui = MinimalKeeperUI()
145
137
  ui.waiting_for_sso_data_key = True
146
138
 
147
139
  ui.on_sso_data_key(step)
@@ -159,7 +151,7 @@ class TestPerformKeeperLogin:
159
151
  params = mocker.MagicMock()
160
152
  params.user = None
161
153
 
162
- _perform_keeper_login(params)
154
+ perform_login(params)
163
155
 
164
156
  assert params.user == "user@example.com"
165
157
  assert params.server == "keepersecurity.com"
@@ -173,7 +165,7 @@ class TestPerformKeeperLogin:
173
165
  params = mocker.MagicMock()
174
166
  params.user = None
175
167
 
176
- _perform_keeper_login(params)
168
+ perform_login(params)
177
169
 
178
170
  assert params.user == "user@example.com"
179
171
  assert params.server == "keepersecurity.com"
@@ -188,7 +180,7 @@ class TestPerformKeeperLogin:
188
180
  params.user = None
189
181
 
190
182
  with pytest.raises(KeyError, match="cancelled by user"):
191
- _perform_keeper_login(params)
183
+ perform_login(params)
192
184
 
193
185
 
194
186
  class TestGetKeeperPassword:
@@ -200,15 +192,15 @@ class TestGetKeeperPassword:
200
192
  mocker.patch("jwbmisc.keeper.KeeperParams", return_value=mock_params)
201
193
 
202
194
  mock_api = mocker.patch("jwbmisc.keeper.api")
203
- mock_vault = mocker.patch("jwbmisc.keeper.keeper_vault")
195
+ mock_vault = mocker.patch("jwbmisc.keeper.vault")
204
196
 
205
197
  mock_record = mocker.MagicMock()
206
198
  mock_vault.KeeperRecord.load.return_value = mock_record
207
199
 
208
- mocker.patch("jwbmisc.keeper._extract_keeper_field", return_value="the_password")
209
- mocker.patch("jwbmisc.keeper._perform_keeper_login")
200
+ mocker.patch("jwbmisc.keeper.extract_record_field", return_value="the_password")
201
+ mocker.patch("jwbmisc.keeper.perform_login")
210
202
 
211
- result = get_keeper_password("RECORD123", "password")
203
+ result = get_password("RECORD123", "password")
212
204
 
213
205
  assert result == "the_password"
214
206
  mock_api.sync_down.assert_called_once()
@@ -221,14 +213,14 @@ class TestGetKeeperPassword:
221
213
  mocker.patch("jwbmisc.keeper.KeeperParams", return_value=mock_params)
222
214
 
223
215
  mocker.patch("jwbmisc.keeper.api")
224
- mock_vault = mocker.patch("jwbmisc.keeper.keeper_vault")
216
+ mock_vault = mocker.patch("jwbmisc.keeper.vault")
225
217
  mock_vault.KeeperRecord.load.return_value = mocker.MagicMock()
226
218
 
227
- mocker.patch("jwbmisc.keeper._extract_keeper_field", return_value=None)
228
- mocker.patch("jwbmisc.keeper._perform_keeper_login")
219
+ mocker.patch("jwbmisc.keeper.extract_record_field", return_value=None)
220
+ mocker.patch("jwbmisc.keeper.perform_login")
229
221
 
230
222
  with pytest.raises(KeyError, match="Field.*not found"):
231
- get_keeper_password("RECORD123", "missing_field")
223
+ get_password("RECORD123", "missing_field")
232
224
 
233
225
  def test_sync_failure_raises(self, mocker, tmp_path):
234
226
  mocker.patch("jwbmisc.keeper.Path.home", return_value=tmp_path)
@@ -240,10 +232,10 @@ class TestGetKeeperPassword:
240
232
  mock_api = mocker.patch("jwbmisc.keeper.api")
241
233
  mock_api.sync_down.side_effect = Exception("Sync failed")
242
234
 
243
- mocker.patch("jwbmisc.keeper._perform_keeper_login")
235
+ mocker.patch("jwbmisc.keeper.perform_login")
244
236
 
245
237
  with pytest.raises(KeyError, match="Failed to sync"):
246
- get_keeper_password("RECORD123", "password")
238
+ get_password("RECORD123", "password")
247
239
 
248
240
  # def test_keeper_url(self, mocker):
249
241
  # mock_keeper = mocker.patch("jwbmisc.passwd._keeper_password")
@@ -259,8 +251,8 @@ class TestGetKeeperPassword:
259
251
 
260
252
 
261
253
  # class TestKeeperPassword:
262
- # def test_delegates_to_get_keeper_password(self, mocker):
263
- # mock_get = mocker.patch("jwbmisc.keeper.get_keeper_password")
254
+ # def test_delegates_to_get_password(self, mocker):
255
+ # mock_get = mocker.patch("jwbmisc.keeper.get_password")
264
256
  # mock_get.return_value = "keeper_secret"
265
257
 
266
258
  # result = _keeper_password("RECORD_UID", "field/path")
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes