Skip to content

Commit 444239d

Browse files
move parse_verification_key function to dispatch.signature module
Signed-off-by: Achille Roussel <achille.roussel@gmail.com>
1 parent 1ddf10c commit 444239d

File tree

4 files changed

+89
-60
lines changed

4 files changed

+89
-60
lines changed

src/dispatch/__init__.py

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,19 +10,27 @@
1010
from dispatch.status import Status
1111

1212
__all__ = [
13+
"Call",
1314
"Client",
14-
"DispatchID",
1515
"DEFAULT_API_URL",
16+
"DispatchID",
17+
"Error",
1618
"Input",
1719
"Output",
18-
"Call",
19-
"Error",
20+
"Registry",
2021
"Reset",
2122
"Status",
22-
"call",
23-
"gather",
2423
"all",
2524
"any",
25+
"call",
26+
"function",
27+
"gather",
2628
"race",
27-
"Registry",
29+
"run",
2830
]
31+
32+
function = None
33+
primitive_function = None
34+
35+
36+
def run(): ...

src/dispatch/fastapi.py

Lines changed: 7 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
"""Integration of Dispatch programmable endpoints for FastAPI.
1+
"""Integration of Dispatch functions with FastAPI.
22
33
Example:
44
@@ -18,7 +18,6 @@ def read_root():
1818
"""
1919

2020
import asyncio
21-
import base64
2221
import logging
2322
import os
2423
from datetime import timedelta
@@ -36,8 +35,7 @@ def read_root():
3635
CaseInsensitiveDict,
3736
Ed25519PublicKey,
3837
Request,
39-
public_key_from_bytes,
40-
public_key_from_pem,
38+
parse_verification_key,
4139
verify_request,
4240
)
4341
from dispatch.status import Status
@@ -46,9 +44,7 @@ def read_root():
4644

4745

4846
class Dispatch(Registry):
49-
"""A Dispatch programmable endpoint, powered by FastAPI."""
50-
51-
__slots__ = ("client",)
47+
"""A Dispatch instance, powered by FastAPI."""
5248

5349
def __init__(
5450
self,
@@ -65,9 +61,9 @@ def __init__(
6561
Args:
6662
app: The FastAPI app to configure.
6763
68-
endpoint: Full URL of the application the Dispatch programmable
69-
endpoint will be running on. Uses the value of the
70-
DISPATCH_ENDPOINT_URL environment variable by default.
64+
endpoint: Full URL of the application the Dispatch instance will
65+
be running on. Uses the value of the DISPATCH_ENDPOINT_URL
66+
environment variable by default.
7167
7268
verification_key: Key to use when verifying signed requests. Uses
7369
the value of the DISPATCH_VERIFICATION_KEY environment variable
@@ -108,55 +104,13 @@ def __init__(
108104
f"{endpoint_from} must be a full URL with protocol and domain (e.g., https://example.com)"
109105
)
110106

111-
verification_key = parse_verification_key(verification_key)
112-
if verification_key:
113-
base64_key = base64.b64encode(verification_key.public_bytes_raw()).decode()
114-
logger.info("verifying request signatures using key %s", base64_key)
115-
elif parsed_url.scheme != "bridge":
116-
logger.warning(
117-
"request verification is disabled because DISPATCH_VERIFICATION_KEY is not set"
118-
)
119-
120107
super().__init__(endpoint, api_key=api_key, api_url=api_url)
121108

109+
verification_key = parse_verification_key(verification_key, url_scheme=parsed_url.scheme)
122110
function_service = _new_app(self, verification_key)
123111
app.mount("/dispatch.sdk.v1.FunctionService", function_service)
124112

125113

126-
def parse_verification_key(
127-
verification_key: Optional[Union[Ed25519PublicKey, str, bytes]],
128-
) -> Optional[Ed25519PublicKey]:
129-
if isinstance(verification_key, Ed25519PublicKey):
130-
return verification_key
131-
132-
from_env = False
133-
if not verification_key:
134-
try:
135-
verification_key = os.environ["DISPATCH_VERIFICATION_KEY"]
136-
except KeyError:
137-
return None
138-
from_env = True
139-
140-
if isinstance(verification_key, bytes):
141-
verification_key = verification_key.decode()
142-
143-
# Be forgiving when accepting keys in PEM format, which may span
144-
# multiple lines. Users attempting to pass a PEM key via an environment
145-
# variable may accidentally include literal "\n" bytes rather than a
146-
# newline char (0xA).
147-
try:
148-
return public_key_from_pem(verification_key.replace("\\n", "\n"))
149-
except ValueError:
150-
pass
151-
152-
try:
153-
return public_key_from_bytes(base64.b64decode(verification_key.encode()))
154-
except ValueError:
155-
if from_env:
156-
raise ValueError(f"invalid DISPATCH_VERIFICATION_KEY '{verification_key}'")
157-
raise ValueError(f"invalid verification key '{verification_key}'")
158-
159-
160114
class _ConnectResponse(fastapi.Response):
161115
media_type = "application/grpc+proto"
162116

src/dispatch/grpc.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
"""Integration of Dispatch functions with gRPC."""
2+
3+
from dispatch.function import Batch, Registry
4+
5+
6+
class Dispatch(Registry):
7+
"""A Dispatch instance to be serviced by a gRPC server."""

src/dispatch/signature/__init__.py

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1+
import base64
12
import logging
3+
import os
24
from datetime import datetime, timedelta
3-
from typing import Sequence, Set, cast
5+
from typing import Optional, Sequence, Set, Union, cast
46

57
import http_sfv
68
from cryptography.hazmat.primitives.asymmetric.ed25519 import (
@@ -123,3 +125,61 @@ def extract_covered_components(result: VerifyResult) -> Set[str]:
123125
covered_components.add(item.value)
124126

125127
return covered_components
128+
129+
130+
def parse_verification_key(
131+
verification_key: Optional[Union[Ed25519PublicKey, str, bytes]],
132+
url_scheme: str | None = None,
133+
) -> Optional[Ed25519PublicKey]:
134+
# This function depends a lot on global context like enviornment variables
135+
# and logging configuration. It's not ideal for testing, but it's useful to
136+
# unify the behavior of the Dispatch class everywhere the signature module
137+
# is used.
138+
if isinstance(verification_key, Ed25519PublicKey):
139+
return verification_key
140+
141+
# Keep track of whether the key was obtained from the environment, so that
142+
# we can tweak the error messages accordingly.
143+
from_env = False
144+
if not verification_key:
145+
try:
146+
verification_key = os.environ["DISPATCH_VERIFICATION_KEY"]
147+
except KeyError:
148+
return None
149+
from_env = verification_key is not None
150+
151+
if isinstance(verification_key, bytes):
152+
verification_key = verification_key.decode()
153+
154+
# Be forgiving when accepting keys in PEM format, which may span
155+
# multiple lines. Users attempting to pass a PEM key via an environment
156+
# variable may accidentally include literal "\n" bytes rather than a
157+
# newline char (0xA).
158+
public_key: Optional[Ed25519PublicKey] = None
159+
try:
160+
public_key = public_key_from_pem(verification_key.replace("\\n", "\n"))
161+
except ValueError:
162+
pass
163+
164+
# If the key is not in PEM format, try to decode it as base64 string.
165+
if not public_key:
166+
try:
167+
public_key = public_key_from_bytes(
168+
base64.b64decode(verification_key.encode())
169+
)
170+
except ValueError:
171+
if from_env:
172+
raise ValueError(
173+
f"invalid DISPATCH_VERIFICATION_KEY '{verification_key}'"
174+
)
175+
raise ValueError(f"invalid verification key '{verification_key}'")
176+
177+
# Print diagostic information about the key, this is useful for debugging.
178+
if public_key:
179+
base64_key = base64.b64encode(public_key.public_bytes_raw()).decode()
180+
logger.info("verifying request signatures using key %s", base64_key)
181+
elif url_scheme != "bridge":
182+
logger.warning(
183+
"request verification is disabled because DISPATCH_VERIFICATION_KEY is not set"
184+
)
185+
return public_key

0 commit comments

Comments
 (0)