Skip to content

Commit f25ec12

Browse files
committed
adds signature verification implementations
1 parent 00fc8a6 commit f25ec12

7 files changed

Lines changed: 87 additions & 3 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,3 +162,4 @@ cython_debug/
162162
# Visual Studio Code
163163
.vscode/
164164
.qodo
165+
*~

apimatic_core/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,6 @@
1212
'logger',
1313
'exceptions',
1414
'constants',
15-
'pagination'
15+
'pagination',
16+
'security'
1617
]

apimatic_core/security/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
__all__=[
2+
'hmac_signature_verifier'
3+
]
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import hashlib
2+
import hmac
3+
from typing import Mapping
4+
5+
from apimatic_core_interfaces.security.signature_verifier import SignatureVerifier
6+
7+
8+
class HmacSignatureVerifier(SignatureVerifier):
9+
"""
10+
Verifier that checks webhook signatures using HMAC-SHA256.
11+
"""
12+
13+
def __init__(self, key: str, signature_header: str):
14+
"""
15+
Initialize the HMAC signature verifier.
16+
17+
Args:
18+
key (str): Secret key used for HMAC verification.
19+
signature_header (str): Name of the HTTP header that carries the signature.
20+
21+
Raises:
22+
ValueError: If key or signature_header is empty.
23+
"""
24+
if not key:
25+
raise ValueError("HMAC verification requires a non-empty key.")
26+
if not signature_header:
27+
raise ValueError("signature_header must be a non-empty string.")
28+
self._key = key
29+
self._signature_header = signature_header.lower()
30+
31+
def verify(self, headers: Mapping[str, str], payload: str) -> bool:
32+
"""
33+
Verify the payload against the signature in headers.
34+
35+
Args:
36+
headers (Mapping[str, str]): HTTP headers of the request.
37+
payload (str): Raw request body as a JSON string.
38+
39+
Returns:
40+
bool: True if the computed HMAC matches the provided signature.
41+
42+
Raises:
43+
ValueError: If the signature header is missing.
44+
"""
45+
normalized = {k.lower(): v for k, v in headers.items()}
46+
provided = normalized.get(self._signature_header)
47+
if not provided:
48+
raise ValueError(f"Missing required header: {self._signature_header}")
49+
50+
computed = hmac.new(
51+
self._key.encode("utf-8"),
52+
payload.encode("utf-8"),
53+
hashlib.sha256
54+
).hexdigest()
55+
56+
return hmac.compare_digest(provided, computed)
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
from typing import Mapping
2+
3+
from apimatic_core_interfaces.security.signature_verifier import SignatureVerifier
4+
5+
6+
class NoOpSignatureVerifier(SignatureVerifier):
7+
"""
8+
Verifier that always returns True.
9+
Useful for testing or when verification is disabled.
10+
"""
11+
12+
def verify(self, headers: Mapping[str, str], payload: str) -> bool:
13+
"""
14+
Always returns True regardless of input.
15+
16+
Args:
17+
headers (Mapping[str, str]): HTTP headers (ignored).
18+
payload (str): Raw request body (ignored).
19+
20+
Returns:
21+
bool: Always True.
22+
"""
23+
return True

requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
jsonpickle~=3.3.0
22
python-dateutil~=2.8
3-
apimatic-core-interfaces~=0.1.0, >= 0.1.5
3+
git+https://github.com/apimatic/core-interfaces-python@webhooks-support
44
requests~=2.31
55
setuptools>=68.0.0
66
jsonpointer~=2.3

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212

1313
setup(
1414
name='apimatic-core',
15-
version='0.2.21',
15+
version='0.2.22',
1616
description='A library that contains core logic and utilities for '
1717
'consuming REST APIs using Python SDKs generated by APIMatic.',
1818
long_description=long_description,

0 commit comments

Comments
 (0)