Skip to content

Commit 7057eee

Browse files
feat(declarativeSettings): sensitive field type support (#361)
Added `sensitive` field to Preferences settings and to Declarative AppConfig one. --------- Signed-off-by: Andrey Borysenko <[email protected]> Signed-off-by: bigcat88 <[email protected]> Co-authored-by: bigcat88 <[email protected]>
1 parent dc733eb commit 7057eee

File tree

4 files changed

+109
-83
lines changed

4 files changed

+109
-83
lines changed

nc_py_api/_preferences_ex.py

Lines changed: 32 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,21 @@ def delete(self, keys: str | list[str], not_fail=True) -> None:
6161
if not not_fail:
6262
raise e from None
6363

64+
def set_value(self, key: str, value: str, sensitive: bool | None = None) -> None:
65+
"""Sets a value and if specified the sensitive flag for a key.
66+
67+
.. note:: A sensitive flag ensures key value are encrypted and truncated in Nextcloud logs.
68+
Default for new records is ``False`` when sensitive is *unspecified*, if changes existing record and
69+
sensitive is *unspecified* it will not change the existing `sensitive` flag.
70+
"""
71+
if not key:
72+
raise ValueError("`key` parameter can not be empty")
73+
require_capabilities("app_api", self._session.capabilities)
74+
params: dict = {"configKey": key, "configValue": value}
75+
if sensitive is not None:
76+
params["sensitive"] = sensitive
77+
self._session.ocs("POST", f"{self._session.ae_url}/{self._url_suffix}", json=params)
78+
6479

6580
class _AsyncBasicAppCfgPref:
6681
_url_suffix: str
@@ -104,72 +119,41 @@ async def delete(self, keys: str | list[str], not_fail=True) -> None:
104119
if not not_fail:
105120
raise e from None
106121

122+
async def set_value(self, key: str, value: str, sensitive: bool | None = None) -> None:
123+
"""Sets a value and if specified the sensitive flag for a key.
124+
125+
.. note:: A sensitive flag ensures key value are encrypted and truncated in Nextcloud logs.
126+
Default for new records is ``False`` when sensitive is *unspecified*, if changes existing record and
127+
sensitive is *unspecified* it will not change the existing `sensitive` flag.
128+
"""
129+
if not key:
130+
raise ValueError("`key` parameter can not be empty")
131+
require_capabilities("app_api", await self._session.capabilities)
132+
params: dict = {"configKey": key, "configValue": value}
133+
if sensitive is not None:
134+
params["sensitive"] = sensitive
135+
await self._session.ocs("POST", f"{self._session.ae_url}/{self._url_suffix}", json=params)
136+
107137

108138
class PreferencesExAPI(_BasicAppCfgPref):
109-
"""User specific preferences API, avalaible as **nc.preferences_ex.<method>**."""
139+
"""User specific preferences API, available as **nc.preferences_ex.<method>**."""
110140

111141
_url_suffix = "ex-app/preference"
112142

113-
def set_value(self, key: str, value: str) -> None:
114-
"""Sets a value for a key."""
115-
if not key:
116-
raise ValueError("`key` parameter can not be empty")
117-
require_capabilities("app_api", self._session.capabilities)
118-
params = {"configKey": key, "configValue": value}
119-
self._session.ocs("POST", f"{self._session.ae_url}/{self._url_suffix}", json=params)
120-
121143

122144
class AsyncPreferencesExAPI(_AsyncBasicAppCfgPref):
123145
"""User specific preferences API."""
124146

125147
_url_suffix = "ex-app/preference"
126148

127-
async def set_value(self, key: str, value: str) -> None:
128-
"""Sets a value for a key."""
129-
if not key:
130-
raise ValueError("`key` parameter can not be empty")
131-
require_capabilities("app_api", await self._session.capabilities)
132-
params = {"configKey": key, "configValue": value}
133-
await self._session.ocs("POST", f"{self._session.ae_url}/{self._url_suffix}", json=params)
134-
135149

136150
class AppConfigExAPI(_BasicAppCfgPref):
137-
"""Non-user(App) specific preferences API, avalaible as **nc.appconfig_ex.<method>**."""
151+
"""Non-user(App) specific preferences API, available as **nc.appconfig_ex.<method>**."""
138152

139153
_url_suffix = "ex-app/config"
140154

141-
def set_value(self, key: str, value: str, sensitive: bool | None = None) -> None:
142-
"""Sets a value and if specified the sensitive flag for a key.
143-
144-
.. note:: A sensitive flag ensures key values are truncated in Nextcloud logs.
145-
Default for new records is ``False`` when sensitive is *unspecified*, if changes existing record and
146-
sensitive is *unspecified* it will not change the existing `sensitive` flag.
147-
"""
148-
if not key:
149-
raise ValueError("`key` parameter can not be empty")
150-
require_capabilities("app_api", self._session.capabilities)
151-
params: dict = {"configKey": key, "configValue": value}
152-
if sensitive is not None:
153-
params["sensitive"] = sensitive
154-
self._session.ocs("POST", f"{self._session.ae_url}/{self._url_suffix}", json=params)
155-
156155

157156
class AsyncAppConfigExAPI(_AsyncBasicAppCfgPref):
158157
"""Non-user(App) specific preferences API."""
159158

160159
_url_suffix = "ex-app/config"
161-
162-
async def set_value(self, key: str, value: str, sensitive: bool | None = None) -> None:
163-
"""Sets a value and if specified the sensitive flag for a key.
164-
165-
.. note:: A sensitive flag ensures key values are truncated in Nextcloud logs.
166-
Default for new records is ``False`` when sensitive is *unspecified*, if changes existing record and
167-
sensitive is *unspecified* it will not change the existing `sensitive` flag.
168-
"""
169-
if not key:
170-
raise ValueError("`key` parameter can not be empty")
171-
require_capabilities("app_api", await self._session.capabilities)
172-
params: dict = {"configKey": key, "configValue": value}
173-
if sensitive is not None:
174-
params["sensitive"] = sensitive
175-
await self._session.ocs("POST", f"{self._session.ae_url}/{self._url_suffix}", json=params)

nc_py_api/_version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
"""Version of nc_py_api."""
22

3-
__version__ = "0.20.1"
3+
__version__ = "0.20.2"

nc_py_api/ex_app/ui/settings.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ class SettingsField:
4848
description: str = ""
4949
placeholder: str = ""
5050
label: str = ""
51+
sensitive: bool = False
5152
notify = False # to be supported in future
5253

5354
@classmethod
@@ -74,6 +75,7 @@ def to_dict(self) -> dict:
7475
"placeholder": self.placeholder,
7576
"label": self.label,
7677
"notify": self.notify,
78+
"sensitive": self.sensitive,
7779
}
7880

7981

tests/actual_tests/appcfg_prefs_ex_test.py

Lines changed: 74 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -233,60 +233,100 @@ async def test_cfg_ex_get_typing_async(class_to_test):
233233
assert r[1].value == "321"
234234

235235

236-
def test_appcfg_sensitive(nc_app):
237-
appcfg = nc_app.appconfig_ex
238-
appcfg.delete("test_key")
239-
appcfg.set_value("test_key", "123", sensitive=True)
240-
assert appcfg.get_value("test_key") == "123"
241-
assert appcfg.get_values(["test_key"])[0].value == "123"
242-
appcfg.delete("test_key")
236+
@pytest.mark.parametrize("class_to_test", (NC_APP.appconfig_ex, NC_APP.preferences_ex))
237+
def test_appcfg_sensitive(nc_app, class_to_test):
238+
class_to_test.delete("test_key")
239+
class_to_test.set_value("test_key", "123", sensitive=True)
240+
assert class_to_test.get_value("test_key") == "123"
241+
assert class_to_test.get_values(["test_key"])[0].value == "123"
242+
class_to_test.delete("test_key")
243243
# next code tests `sensitive` value from the `AppAPI`
244244
params = {"configKey": "test_key", "configValue": "123"}
245-
result = nc_app._session.ocs("POST", f"{nc_app._session.ae_url}/{appcfg._url_suffix}", json=params)
246-
assert not result["sensitive"] # by default if sensitive value is unspecified it is False
247-
appcfg.delete("test_key")
248-
params = {"configKey": "test_key", "configValue": "123", "sensitive": True}
249-
result = nc_app._session.ocs("POST", f"{nc_app._session.ae_url}/{appcfg._url_suffix}", json=params)
245+
result = nc_app._session.ocs("POST", f"{nc_app._session.ae_url}/{class_to_test._url_suffix}", json=params)
246+
if class_to_test == NC_APP.appconfig_ex or nc_app.srv_version["major"] >= 32:
247+
assert not result["sensitive"] # by default if sensitive value is unspecified it is False
248+
class_to_test.delete("test_key")
249+
params["sensitive"] = True
250+
result = nc_app._session.ocs("POST", f"{nc_app._session.ae_url}/{class_to_test._url_suffix}", json=params)
250251
assert result["configkey"] == "test_key"
251252
assert result["configvalue"] == "123"
252-
assert bool(result["sensitive"]) is True
253+
if class_to_test == NC_APP.appconfig_ex or nc_app.srv_version["major"] >= 32:
254+
assert bool(result["sensitive"]) is True
253255
params.pop("sensitive") # if we not specify value, AppEcosystem should not change it.
254-
result = nc_app._session.ocs("POST", f"{nc_app._session.ae_url}/{appcfg._url_suffix}", json=params)
256+
result = nc_app._session.ocs("POST", f"{nc_app._session.ae_url}/{class_to_test._url_suffix}", json=params)
255257
assert result["configkey"] == "test_key"
256258
assert result["configvalue"] == "123"
257-
assert bool(result["sensitive"]) is True
259+
if class_to_test == NC_APP.appconfig_ex or nc_app.srv_version["major"] >= 32:
260+
assert bool(result["sensitive"]) is True
258261
params["sensitive"] = False
259-
result = nc_app._session.ocs("POST", f"{nc_app._session.ae_url}/{appcfg._url_suffix}", json=params)
262+
result = nc_app._session.ocs("POST", f"{nc_app._session.ae_url}/{class_to_test._url_suffix}", json=params)
260263
assert result["configkey"] == "test_key"
261264
assert result["configvalue"] == "123"
262-
assert bool(result["sensitive"]) is False
265+
if class_to_test == NC_APP.appconfig_ex or nc_app.srv_version["major"] >= 32:
266+
assert bool(result["sensitive"]) is False
267+
# test setting to empty value (sensitive=False)
268+
params["configValue"] = ""
269+
result = nc_app._session.ocs("POST", f"{nc_app._session.ae_url}/{class_to_test._url_suffix}", json=params)
270+
assert result["configkey"] == "test_key"
271+
assert result["configvalue"] == ""
272+
if class_to_test == NC_APP.appconfig_ex or nc_app.srv_version["major"] >= 32:
273+
assert bool(result["sensitive"]) is False
274+
assert class_to_test.get_value("test_key") == ""
275+
# test setting to empty value (sensitive=True)
276+
params["sensitive"] = True
277+
result = nc_app._session.ocs("POST", f"{nc_app._session.ae_url}/{class_to_test._url_suffix}", json=params)
278+
assert result["configkey"] == "test_key"
279+
assert result["configvalue"] == ""
280+
if class_to_test == NC_APP.appconfig_ex or nc_app.srv_version["major"] >= 32:
281+
assert bool(result["sensitive"]) is True
282+
assert class_to_test.get_value("test_key") == ""
263283

264284

265285
@pytest.mark.asyncio(scope="session")
266-
async def test_appcfg_sensitive_async(anc_app):
267-
appcfg = anc_app.appconfig_ex
268-
await appcfg.delete("test_key")
269-
await appcfg.set_value("test_key", "123", sensitive=True)
270-
assert await appcfg.get_value("test_key") == "123"
271-
assert (await appcfg.get_values(["test_key"]))[0].value == "123"
272-
await appcfg.delete("test_key")
286+
@pytest.mark.parametrize("class_to_test", (NC_APP_ASYNC.appconfig_ex, NC_APP_ASYNC.preferences_ex))
287+
async def test_appcfg_sensitive_async(anc_app, class_to_test):
288+
await class_to_test.delete("test_key")
289+
await class_to_test.set_value("test_key", "123", sensitive=True)
290+
assert await class_to_test.get_value("test_key") == "123"
291+
assert (await class_to_test.get_values(["test_key"]))[0].value == "123"
292+
await class_to_test.delete("test_key")
273293
# next code tests `sensitive` value from the `AppAPI`
274294
params = {"configKey": "test_key", "configValue": "123"}
275-
result = await anc_app._session.ocs("POST", f"{anc_app._session.ae_url}/{appcfg._url_suffix}", json=params)
276-
assert not result["sensitive"] # by default if sensitive value is unspecified it is False
277-
await appcfg.delete("test_key")
278-
params = {"configKey": "test_key", "configValue": "123", "sensitive": True}
279-
result = await anc_app._session.ocs("POST", f"{anc_app._session.ae_url}/{appcfg._url_suffix}", json=params)
295+
result = await anc_app._session.ocs("POST", f"{anc_app._session.ae_url}/{class_to_test._url_suffix}", json=params)
296+
if class_to_test == NC_APP_ASYNC.appconfig_ex or (await anc_app.srv_version)["major"] >= 32:
297+
assert not result["sensitive"] # by default if sensitive value is unspecified it is False
298+
await class_to_test.delete("test_key")
299+
params["sensitive"] = True
300+
result = await anc_app._session.ocs("POST", f"{anc_app._session.ae_url}/{class_to_test._url_suffix}", json=params)
280301
assert result["configkey"] == "test_key"
281302
assert result["configvalue"] == "123"
282-
assert bool(result["sensitive"]) is True
303+
if class_to_test == NC_APP_ASYNC.appconfig_ex or (await anc_app.srv_version)["major"] >= 32:
304+
assert bool(result["sensitive"]) is True
283305
params.pop("sensitive") # if we not specify value, AppEcosystem should not change it.
284-
result = await anc_app._session.ocs("POST", f"{anc_app._session.ae_url}/{appcfg._url_suffix}", json=params)
306+
result = await anc_app._session.ocs("POST", f"{anc_app._session.ae_url}/{class_to_test._url_suffix}", json=params)
285307
assert result["configkey"] == "test_key"
286308
assert result["configvalue"] == "123"
287-
assert bool(result["sensitive"]) is True
309+
if class_to_test == NC_APP_ASYNC.appconfig_ex or (await anc_app.srv_version)["major"] >= 32:
310+
assert bool(result["sensitive"]) is True
288311
params["sensitive"] = False
289-
result = await anc_app._session.ocs("POST", f"{anc_app._session.ae_url}/{appcfg._url_suffix}", json=params)
312+
result = await anc_app._session.ocs("POST", f"{anc_app._session.ae_url}/{class_to_test._url_suffix}", json=params)
290313
assert result["configkey"] == "test_key"
291314
assert result["configvalue"] == "123"
292-
assert bool(result["sensitive"]) is False
315+
if class_to_test == NC_APP_ASYNC.appconfig_ex or (await anc_app.srv_version)["major"] >= 32:
316+
assert bool(result["sensitive"]) is False
317+
# test setting to empty value (sensitive=False)
318+
params["configValue"] = ""
319+
result = await anc_app._session.ocs("POST", f"{anc_app._session.ae_url}/{class_to_test._url_suffix}", json=params)
320+
assert result["configkey"] == "test_key"
321+
assert result["configvalue"] == ""
322+
if class_to_test == NC_APP_ASYNC.appconfig_ex or (await anc_app.srv_version)["major"] >= 32:
323+
assert bool(result["sensitive"]) is False
324+
assert await class_to_test.get_value("test_key") == ""
325+
# test setting to empty value (sensitive=True)
326+
params["sensitive"] = True
327+
result = await anc_app._session.ocs("POST", f"{anc_app._session.ae_url}/{class_to_test._url_suffix}", json=params)
328+
assert result["configkey"] == "test_key"
329+
assert result["configvalue"] == ""
330+
if class_to_test == NC_APP_ASYNC.appconfig_ex or (await anc_app.srv_version)["major"] >= 32:
331+
assert bool(result["sensitive"]) is True
332+
assert await class_to_test.get_value("test_key") == ""

0 commit comments

Comments
 (0)