datasette-secrets 0.1a4__tar.gz → 0.2__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.
Potentially problematic release.
This version of datasette-secrets might be problematic. Click here for more details.
- {datasette_secrets-0.1a4 → datasette_secrets-0.2}/PKG-INFO +3 -2
- {datasette_secrets-0.1a4 → datasette_secrets-0.2}/datasette_secrets/__init__.py +20 -11
- {datasette_secrets-0.1a4 → datasette_secrets-0.2}/datasette_secrets.egg-info/PKG-INFO +3 -2
- {datasette_secrets-0.1a4 → datasette_secrets-0.2}/datasette_secrets.egg-info/requires.txt +2 -1
- {datasette_secrets-0.1a4 → datasette_secrets-0.2}/pyproject.toml +3 -3
- {datasette_secrets-0.1a4 → datasette_secrets-0.2}/tests/test_secrets.py +27 -17
- {datasette_secrets-0.1a4 → datasette_secrets-0.2}/LICENSE +0 -0
- {datasette_secrets-0.1a4 → datasette_secrets-0.2}/README.md +0 -0
- {datasette_secrets-0.1a4 → datasette_secrets-0.2}/datasette_secrets/hookspecs.py +0 -0
- {datasette_secrets-0.1a4 → datasette_secrets-0.2}/datasette_secrets/templates/secrets_index.html +0 -0
- {datasette_secrets-0.1a4 → datasette_secrets-0.2}/datasette_secrets/templates/secrets_update.html +0 -0
- {datasette_secrets-0.1a4 → datasette_secrets-0.2}/datasette_secrets.egg-info/SOURCES.txt +0 -0
- {datasette_secrets-0.1a4 → datasette_secrets-0.2}/datasette_secrets.egg-info/dependency_links.txt +0 -0
- {datasette_secrets-0.1a4 → datasette_secrets-0.2}/datasette_secrets.egg-info/entry_points.txt +0 -0
- {datasette_secrets-0.1a4 → datasette_secrets-0.2}/datasette_secrets.egg-info/top_level.txt +0 -0
- {datasette_secrets-0.1a4 → datasette_secrets-0.2}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: datasette-secrets
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2
|
|
4
4
|
Summary: Manage secrets such as API keys for use with other Datasette plugins
|
|
5
5
|
Author: Datasette
|
|
6
6
|
License: Apache-2.0
|
|
@@ -13,11 +13,12 @@ Classifier: License :: OSI Approved :: Apache Software License
|
|
|
13
13
|
Requires-Python: >=3.8
|
|
14
14
|
Description-Content-Type: text/markdown
|
|
15
15
|
License-File: LICENSE
|
|
16
|
-
Requires-Dist: datasette
|
|
16
|
+
Requires-Dist: datasette
|
|
17
17
|
Requires-Dist: cryptography
|
|
18
18
|
Provides-Extra: test
|
|
19
19
|
Requires-Dist: pytest; extra == "test"
|
|
20
20
|
Requires-Dist: pytest-asyncio; extra == "test"
|
|
21
|
+
Requires-Dist: datasette-test>=0.3.2; extra == "test"
|
|
21
22
|
|
|
22
23
|
# datasette-secrets
|
|
23
24
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import click
|
|
2
2
|
from cryptography.fernet import Fernet
|
|
3
3
|
import dataclasses
|
|
4
|
-
from datasette import hookimpl, Forbidden,
|
|
4
|
+
from datasette import hookimpl, Forbidden, Response
|
|
5
5
|
from datasette.plugins import pm
|
|
6
6
|
from datasette.utils import await_me_maybe, sqlite3
|
|
7
7
|
import os
|
|
@@ -89,7 +89,7 @@ create table if not exists datasette_secrets (
|
|
|
89
89
|
def get_database(datasette):
|
|
90
90
|
plugin_config = datasette.plugin_config("datasette-secrets") or {}
|
|
91
91
|
database = plugin_config.get("database") or "_internal"
|
|
92
|
-
if database == "_internal":
|
|
92
|
+
if database == "_internal" and hasattr(datasette, "get_internal_database"):
|
|
93
93
|
return datasette.get_internal_database()
|
|
94
94
|
return datasette.get_database(database)
|
|
95
95
|
|
|
@@ -108,6 +108,8 @@ def get_config(datasette):
|
|
|
108
108
|
|
|
109
109
|
@hookimpl
|
|
110
110
|
def register_permissions(datasette):
|
|
111
|
+
from datasette import Permission
|
|
112
|
+
|
|
111
113
|
return [
|
|
112
114
|
Permission(
|
|
113
115
|
name="manage-secrets",
|
|
@@ -187,15 +189,22 @@ async def secrets_index(datasette, request):
|
|
|
187
189
|
)
|
|
188
190
|
existing_secrets = {row["name"]: dict(row) for row in existing_secrets_result.rows}
|
|
189
191
|
# Try to turn updated_by into actors
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
192
|
+
if hasattr(datasette, "actors_from_ids"):
|
|
193
|
+
actors = await datasette.actors_from_ids(
|
|
194
|
+
{
|
|
195
|
+
row["updated_by"]
|
|
196
|
+
for row in existing_secrets.values()
|
|
197
|
+
if row["updated_by"]
|
|
198
|
+
}
|
|
199
|
+
)
|
|
200
|
+
for secret in existing_secrets.values():
|
|
201
|
+
if secret["updated_by"]:
|
|
202
|
+
actor = actors.get(secret["updated_by"])
|
|
203
|
+
if actor:
|
|
204
|
+
display = (
|
|
205
|
+
actor.get("username") or actor.get("name") or actor.get("id")
|
|
206
|
+
)
|
|
207
|
+
secret["updated_by"] = display
|
|
199
208
|
unset_secrets = [
|
|
200
209
|
secret
|
|
201
210
|
for secret in all_secrets
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: datasette-secrets
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2
|
|
4
4
|
Summary: Manage secrets such as API keys for use with other Datasette plugins
|
|
5
5
|
Author: Datasette
|
|
6
6
|
License: Apache-2.0
|
|
@@ -13,11 +13,12 @@ Classifier: License :: OSI Approved :: Apache Software License
|
|
|
13
13
|
Requires-Python: >=3.8
|
|
14
14
|
Description-Content-Type: text/markdown
|
|
15
15
|
License-File: LICENSE
|
|
16
|
-
Requires-Dist: datasette
|
|
16
|
+
Requires-Dist: datasette
|
|
17
17
|
Requires-Dist: cryptography
|
|
18
18
|
Provides-Extra: test
|
|
19
19
|
Requires-Dist: pytest; extra == "test"
|
|
20
20
|
Requires-Dist: pytest-asyncio; extra == "test"
|
|
21
|
+
Requires-Dist: datasette-test>=0.3.2; extra == "test"
|
|
21
22
|
|
|
22
23
|
# datasette-secrets
|
|
23
24
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "datasette-secrets"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.2"
|
|
4
4
|
description = "Manage secrets such as API keys for use with other Datasette plugins"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
authors = [{name = "Datasette"}]
|
|
@@ -11,7 +11,7 @@ classifiers=[
|
|
|
11
11
|
]
|
|
12
12
|
requires-python = ">=3.8"
|
|
13
13
|
dependencies = [
|
|
14
|
-
"datasette
|
|
14
|
+
"datasette",
|
|
15
15
|
"cryptography"
|
|
16
16
|
]
|
|
17
17
|
|
|
@@ -25,7 +25,7 @@ CI = "https://github.com/datasette/datasette-secrets/actions"
|
|
|
25
25
|
secrets = "datasette_secrets"
|
|
26
26
|
|
|
27
27
|
[project.optional-dependencies]
|
|
28
|
-
test = ["pytest", "pytest-asyncio"]
|
|
28
|
+
test = ["pytest", "pytest-asyncio", "datasette-test>=0.3.2"]
|
|
29
29
|
|
|
30
30
|
[tool.pytest.ini_options]
|
|
31
31
|
asyncio_mode = "strict"
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
from click.testing import CliRunner
|
|
2
2
|
from cryptography.fernet import Fernet
|
|
3
3
|
from datasette import hookimpl
|
|
4
|
-
from datasette.app import Datasette
|
|
5
4
|
from datasette.cli import cli
|
|
6
5
|
from datasette.plugins import pm
|
|
6
|
+
from datasette_test import Datasette, actor_cookie
|
|
7
7
|
from datasette_secrets import get_secret, Secret, startup, get_config
|
|
8
8
|
import pytest
|
|
9
9
|
from unittest.mock import ANY
|
|
@@ -11,6 +11,13 @@ from unittest.mock import ANY
|
|
|
11
11
|
TEST_ENCRYPTION_KEY = "-LujHtwFWGaBpznrV1zduoZBmCnMOW7J0H5hmeXgAVo="
|
|
12
12
|
|
|
13
13
|
|
|
14
|
+
def get_internal_database(ds):
|
|
15
|
+
if hasattr(ds, "get_internal_database"):
|
|
16
|
+
return ds.get_internal_database()
|
|
17
|
+
else:
|
|
18
|
+
return ds.get_database("_internal")
|
|
19
|
+
|
|
20
|
+
|
|
14
21
|
def test_generate_command():
|
|
15
22
|
runner = CliRunner()
|
|
16
23
|
result = runner.invoke(cli, ["secrets", "generate-encryption-key"])
|
|
@@ -89,15 +96,13 @@ def register_multiple_secrets():
|
|
|
89
96
|
@pytest.fixture
|
|
90
97
|
def ds():
|
|
91
98
|
return Datasette(
|
|
92
|
-
|
|
93
|
-
"
|
|
94
|
-
"
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
"permissions": {"manage-secrets": {"id": "admin"}},
|
|
100
|
-
}
|
|
99
|
+
plugin_config={
|
|
100
|
+
"datasette-secrets": {
|
|
101
|
+
"database": "_internal",
|
|
102
|
+
"encryption-key": TEST_ENCRYPTION_KEY,
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
permissions={"manage-secrets": {"id": "admin"}},
|
|
101
106
|
)
|
|
102
107
|
|
|
103
108
|
|
|
@@ -115,7 +120,7 @@ async def test_permissions(ds, path, verb, data, user):
|
|
|
115
120
|
kwargs = {}
|
|
116
121
|
if user:
|
|
117
122
|
kwargs["cookies"] = {
|
|
118
|
-
"ds_actor":
|
|
123
|
+
"ds_actor": actor_cookie(ds, {"id": user}),
|
|
119
124
|
}
|
|
120
125
|
if data:
|
|
121
126
|
kwargs["data"] = data
|
|
@@ -131,7 +136,7 @@ async def test_permissions(ds, path, verb, data, user):
|
|
|
131
136
|
|
|
132
137
|
@pytest.mark.asyncio
|
|
133
138
|
async def test_set_secret(ds, use_actors_plugin):
|
|
134
|
-
cookies = {"ds_actor":
|
|
139
|
+
cookies = {"ds_actor": actor_cookie(ds, {"id": "admin"})}
|
|
135
140
|
get_response = await ds.client.get("/-/secrets/EXAMPLE_SECRET", cookies=cookies)
|
|
136
141
|
csrftoken = get_response.cookies["ds_csrftoken"]
|
|
137
142
|
cookies["ds_csrftoken"] = csrftoken
|
|
@@ -142,7 +147,7 @@ async def test_set_secret(ds, use_actors_plugin):
|
|
|
142
147
|
)
|
|
143
148
|
assert post_response.status_code == 302
|
|
144
149
|
assert post_response.headers["Location"] == "/-/secrets"
|
|
145
|
-
internal_db =
|
|
150
|
+
internal_db = get_internal_database(ds)
|
|
146
151
|
secrets = await internal_db.execute("select * from datasette_secrets")
|
|
147
152
|
rows = [dict(r) for r in secrets.rows]
|
|
148
153
|
assert rows == [
|
|
@@ -174,7 +179,12 @@ async def test_set_secret(ds, use_actors_plugin):
|
|
|
174
179
|
assert response.status_code == 200
|
|
175
180
|
assert "EXAMPLE_SECRET" in response.text
|
|
176
181
|
assert "new-note" in response.text
|
|
177
|
-
|
|
182
|
+
|
|
183
|
+
if hasattr(ds, "actors_from_ids"):
|
|
184
|
+
assert "<td>ADMIN</td>" in response.text
|
|
185
|
+
else:
|
|
186
|
+
# Pre 1.0, so can't use that mechanism
|
|
187
|
+
assert "<td>admin</td>" in response.text
|
|
178
188
|
|
|
179
189
|
# Now let's edit it
|
|
180
190
|
post_response2 = await ds.client.post(
|
|
@@ -213,11 +223,11 @@ async def test_set_secret(ds, use_actors_plugin):
|
|
|
213
223
|
@pytest.mark.asyncio
|
|
214
224
|
async def test_get_secret(ds, monkeypatch):
|
|
215
225
|
# First set it manually
|
|
216
|
-
cookies = {"ds_actor":
|
|
226
|
+
cookies = {"ds_actor": actor_cookie(ds, {"id": "admin"})}
|
|
217
227
|
get_response = await ds.client.get("/-/secrets/EXAMPLE_SECRET", cookies=cookies)
|
|
218
228
|
csrftoken = get_response.cookies["ds_csrftoken"]
|
|
219
229
|
cookies["ds_csrftoken"] = csrftoken
|
|
220
|
-
db =
|
|
230
|
+
db = get_internal_database(ds)
|
|
221
231
|
# Reset state
|
|
222
232
|
await db.execute_write(
|
|
223
233
|
"update datasette_secrets set last_used_at = null, last_used_by = null"
|
|
@@ -288,7 +298,7 @@ async def test_secret_index_page(ds, register_multiple_secrets):
|
|
|
288
298
|
response = await ds.client.get(
|
|
289
299
|
"/-/secrets",
|
|
290
300
|
cookies={
|
|
291
|
-
"ds_actor":
|
|
301
|
+
"ds_actor": actor_cookie(ds, {"id": "admin"}),
|
|
292
302
|
},
|
|
293
303
|
)
|
|
294
304
|
assert response.status_code == 200
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{datasette_secrets-0.1a4 → datasette_secrets-0.2}/datasette_secrets/templates/secrets_index.html
RENAMED
|
File without changes
|
{datasette_secrets-0.1a4 → datasette_secrets-0.2}/datasette_secrets/templates/secrets_update.html
RENAMED
|
File without changes
|
|
File without changes
|
{datasette_secrets-0.1a4 → datasette_secrets-0.2}/datasette_secrets.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
{datasette_secrets-0.1a4 → datasette_secrets-0.2}/datasette_secrets.egg-info/entry_points.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|