Source code for kipu.auth
"""
Authentication module for Kipu API
Implements HMAC SHA1 signature generation as per Kipu API documentation
"""
import base64
import hashlib
import hmac
from email.utils import formatdate
from typing import Dict
[docs]
class KipuAuth:
def __init__(self, access_id: str, secret_key: str, app_id: str, version: int):
"""
Initialize Kipu authentication
Args:
access_id: Your access ID provided by Kipu
secret_key: Your secret key provided by Kipu
app_id: Your app ID (also called recipient_id) provided by Kipu
version: API version (3 for SHA1, 4 for SHA256)
"""
self.access_id = access_id
self.secret_key = secret_key
self.app_id = app_id
self.version = version
[docs]
def generate_signature(
self,
method: str,
uri: str,
date: str,
content_type: str = "",
content_md5: str = "",
) -> str:
"""
Generate HMAC signature for Kipu API request
Uses SHA1 for API v3, SHA256 for API v4
Args:
method: HTTP method (GET, POST, PATCH)
uri: Request URI including query parameters
date: RFC 822 formatted date string
content_type: Content-Type header (for POST requests)
content_md5: MD5 hash of request body (for POST requests)
Returns:
Base64 encoded HMAC signature (algorithm depends on version)
"""
# Build canonical string based on method
if method.upper() == "GET":
# For GET: ,,request_uri,Date
canonical_string = f",,{uri},{date}"
else:
# For POST/PATCH: Content-Type,Content-MD5,request_uri,Date
canonical_string = f"{content_type},{content_md5},{uri},{date}"
# Create HMAC SHA1 hash
hmac_hash = hmac.new(
self.secret_key.encode("utf-8"),
canonical_string.encode("utf-8"),
hashlib.sha1 if self.version <= 3 else hashlib.sha256,
)
# Base64 encode the hash
signature = base64.b64encode(hmac_hash.digest()).decode("utf-8")
return signature
[docs]
def get_auth_headers(
self, method: str, uri: str, body: bytes = b"", content_type: str = ""
) -> Dict[str, str]:
"""
Generate complete authentication headers for Kipu API request
Args:
method: HTTP method
uri: Request URI
body: Request body (for POST requests)
content_type: Content-Type header
Returns:
Dictionary of headers needed for authentication
"""
# Generate RFC 822 date
date = formatdate(timeval=None, localtime=False, usegmt=True)
# Generate Content-MD5 for POST requests
content_md5 = ""
if method.upper() in ["POST", "PATCH"] and body:
md5_hash = hashlib.md5(body) # nosec
content_md5 = base64.b64encode(md5_hash.digest()).decode("utf-8") # nosec
# Generate signature
signature = self.generate_signature(
method, uri, date, content_type, content_md5
)
auth_type = "APIAuth" if self.version <= 3 else "APIAuth-HMAC-SHA256"
# Build headers
headers = {
"Accept": f"application/vnd.kipusystems+json; version={self.version}",
"Authorization": f"{auth_type} {self.access_id}:{signature}",
"Date": date,
}
# Add content headers for POST/PATCH requests
if method.upper() in ["POST", "PATCH"]:
if content_type:
headers["Content-Type"] = content_type
if content_md5:
headers["Content-MD5"] = content_md5
return headers
[docs]
def add_app_id_to_params(self, params: Dict[str, str]) -> Dict[str, str]:
"""
Add app_id to query parameters
Args:
params: Existing query parameters
Returns:
Parameters with app_id added
"""
if params is None:
params = {}
params["app_id"] = self.app_id
return params