datasette-secrets 0.1a3__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.

@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: datasette-secrets
3
- Version: 0.1a3
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>=1.0a13
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, Permission, Response
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
@@ -23,6 +23,9 @@ async def get_secret(datasette, secret_name, actor_id=None):
23
23
  return os.environ[env_var]
24
24
  # Now look it up in the database
25
25
  config = get_config(datasette)
26
+ if config is None:
27
+ return None
28
+ encryption_key = config["encryption_key"]
26
29
  db = get_database(datasette)
27
30
  try:
28
31
  db_secret = (
@@ -35,7 +38,7 @@ async def get_secret(datasette, secret_name, actor_id=None):
35
38
  return None
36
39
  if not db_secret:
37
40
  return None
38
- key = Fernet(config["encryption_key"].encode("utf-8"))
41
+ key = Fernet(encryption_key.encode("utf-8"))
39
42
  decrypted = key.decrypt(db_secret["encrypted"])
40
43
  # Update the last used timestamp and actor_id
41
44
  params = (actor_id, db_secret["id"])
@@ -86,7 +89,7 @@ create table if not exists datasette_secrets (
86
89
  def get_database(datasette):
87
90
  plugin_config = datasette.plugin_config("datasette-secrets") or {}
88
91
  database = plugin_config.get("database") or "_internal"
89
- if database == "_internal":
92
+ if database == "_internal" and hasattr(datasette, "get_internal_database"):
90
93
  return datasette.get_internal_database()
91
94
  return datasette.get_database(database)
92
95
 
@@ -105,6 +108,8 @@ def get_config(datasette):
105
108
 
106
109
  @hookimpl
107
110
  def register_permissions(datasette):
111
+ from datasette import Permission
112
+
108
113
  return [
109
114
  Permission(
110
115
  name="manage-secrets",
@@ -184,15 +189,22 @@ async def secrets_index(datasette, request):
184
189
  )
185
190
  existing_secrets = {row["name"]: dict(row) for row in existing_secrets_result.rows}
186
191
  # Try to turn updated_by into actors
187
- actors = await datasette.actors_from_ids(
188
- {row["updated_by"] for row in existing_secrets.values() if row["updated_by"]}
189
- )
190
- for secret in existing_secrets.values():
191
- if secret["updated_by"]:
192
- actor = actors.get(secret["updated_by"])
193
- if actor:
194
- display = actor.get("username") or actor.get("name") or actor.get("id")
195
- secret["updated_by"] = display
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
196
208
  unset_secrets = [
197
209
  secret
198
210
  for secret in all_secrets
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: datasette-secrets
3
- Version: 0.1a3
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>=1.0a13
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,7 @@
1
- datasette>=1.0a13
1
+ datasette
2
2
  cryptography
3
3
 
4
4
  [test]
5
5
  pytest
6
6
  pytest-asyncio
7
+ datasette-test>=0.3.2
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "datasette-secrets"
3
- version = "0.1a3"
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>=1.0a13",
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,16 +1,23 @@
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
7
- from datasette_secrets import get_secret, Secret, startup
6
+ from datasette_test import Datasette, actor_cookie
7
+ from datasette_secrets import get_secret, Secret, startup, get_config
8
8
  import pytest
9
9
  from unittest.mock import ANY
10
10
 
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
- config={
93
- "plugins": {
94
- "datasette-secrets": {
95
- "database": "_internal",
96
- "encryption-key": TEST_ENCRYPTION_KEY,
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": ds.client.actor_cookie({"id": user}),
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": ds.client.actor_cookie({"id": "admin"})}
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 = ds.get_internal_database()
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
- assert "<td>ADMIN</td>" in response.text
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": ds.client.actor_cookie({"id": "admin"})}
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 = ds.get_internal_database()
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"
@@ -275,12 +285,20 @@ async def test_get_secret(ds, monkeypatch):
275
285
  assert await get_secret(ds, "EXAMPLE_SECRET") is None
276
286
 
277
287
 
288
+ @pytest.mark.asyncio
289
+ async def test_if_not_configured(register_multiple_secrets):
290
+ ds = Datasette()
291
+ config = get_config(ds)
292
+ assert config is None
293
+ assert await get_secret(ds, "OPENAI_API_KEY") is None
294
+
295
+
278
296
  @pytest.mark.asyncio
279
297
  async def test_secret_index_page(ds, register_multiple_secrets):
280
298
  response = await ds.client.get(
281
299
  "/-/secrets",
282
300
  cookies={
283
- "ds_actor": ds.client.actor_cookie({"id": "admin"}),
301
+ "ds_actor": actor_cookie(ds, {"id": "admin"}),
284
302
  },
285
303
  )
286
304
  assert response.status_code == 200