| 11 | |
| 12 | |
| 13 | class SignatureVerifier: |
| 14 | def __init__(self, signing_secret: str, clock: Clock = Clock()): |
| 15 | """Slack request signature verifier |
| 16 | |
| 17 | Slack signs its requests using a secret that's unique to your app. |
| 18 | With the help of signing secrets, your app can more confidently verify |
| 19 | whether requests from us are authentic. |
| 20 | https://docs.slack.dev/authentication/verifying-requests-from-slack/ |
| 21 | """ |
| 22 | self.signing_secret = signing_secret |
| 23 | self.clock = clock |
| 24 | |
| 25 | def is_valid_request( |
| 26 | self, |
| 27 | body: Union[str, bytes], |
| 28 | headers: Dict[str, str], |
| 29 | ) -> bool: |
| 30 | """Verifies if the given signature is valid""" |
| 31 | if headers is None: |
| 32 | return False |
| 33 | normalized_headers = {k.lower(): v for k, v in headers.items()} |
| 34 | return self.is_valid( |
| 35 | body=body, |
| 36 | timestamp=normalized_headers.get("x-slack-request-timestamp", None), |
| 37 | signature=normalized_headers.get("x-slack-signature", None), |
| 38 | ) |
| 39 | |
| 40 | def is_valid( |
| 41 | self, |
| 42 | body: Union[str, bytes], |
| 43 | timestamp: str, |
| 44 | signature: str, |
| 45 | ) -> bool: |
| 46 | """Verifies if the given signature is valid""" |
| 47 | if timestamp is None or signature is None: |
| 48 | return False |
| 49 | |
| 50 | if abs(self.clock.now() - int(timestamp)) > 60 * 5: |
| 51 | return False |
| 52 | |
| 53 | calculated_signature = self.generate_signature(timestamp=timestamp, body=body) |
| 54 | if calculated_signature is None: |
| 55 | return False |
| 56 | return hmac.compare_digest(calculated_signature, signature) |
| 57 | |
| 58 | def generate_signature(self, *, timestamp: str, body: Union[str, bytes]) -> Optional[str]: |
| 59 | """Generates a signature""" |
| 60 | if timestamp is None: |
| 61 | return None |
| 62 | if body is None: |
| 63 | body = "" |
| 64 | if isinstance(body, bytes): |
| 65 | body = body.decode("utf-8") |
| 66 | |
| 67 | format_req = str.encode(f"v0:{timestamp}:{body}") |
| 68 | encoded_secret = str.encode(self.signing_secret) |
| 69 | request_hash = hmac.new(encoded_secret, format_req, hashlib.sha256).hexdigest() |
| 70 | calculated_signature = f"v0={request_hash}" |
no outgoing calls