Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions stripe/_api_requestor.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
from io import BytesIO, IOBase
import functools
import hashlib
import json
import os
import platform
import socket
from typing import (
Any,
AsyncIterable,
Expand Down Expand Up @@ -72,6 +75,19 @@
_default_proxy: Optional[str] = None


@functools.lru_cache(maxsize=None)
def _get_uname_hash() -> Optional[str]:
try:
parts: List[str] = list(platform.uname())
try:
parts.append(socket.gethostname())
except Exception:
pass
return hashlib.md5(" ".join(parts).encode()).hexdigest()
except Exception:
return None


def _maybe_emit_stripe_notice(rheaders: Mapping[str, str]) -> None:
notice = rheaders.get("Stripe-Notice")
if notice:
Expand Down Expand Up @@ -525,6 +541,9 @@ def request_headers(
"lang": "python",
"httplib": self._get_http_client().name,
}
uname_hash = _get_uname_hash()
if uname_hash is not None:
ua["source"] = uname_hash
attr_funcs: List[Tuple[str, Callable[[], str]]] = [
("lang_version", platform.python_version),
]
Expand Down
31 changes: 31 additions & 0 deletions tests/test_api_requestor.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import datetime
import json
import re
import tempfile
import uuid
from collections import OrderedDict
Expand Down Expand Up @@ -788,6 +789,36 @@ def fail():
last_call.get_raw_header("X-Stripe-Client-User-Agent")
)

def test_source_field_is_md5_hex(self, requestor, http_client_mock):
http_client_mock.stub_request(
"get", path=self.v1_path, rbody="{}", rcode=200
)
requestor.request("get", self.v1_path, {}, base_address="api")

last_call = http_client_mock.get_last_call()
client_ua = json.loads(
last_call.get_raw_header("X-Stripe-Client-User-Agent")
)
assert "source" in client_ua
assert re.fullmatch(r"[0-9a-f]{32}", client_ua["source"])

def test_source_field_absent_when_uname_fails(
self, requestor, mocker, http_client_mock
):
http_client_mock.stub_request(
"get", path=self.v1_path, rbody="{}", rcode=200
)
mocker.patch(
"stripe._api_requestor._get_uname_hash", return_value=None
)
requestor.request("get", self.v1_path, {}, base_address="api")

last_call = http_client_mock.get_last_call()
client_ua = json.loads(
last_call.get_raw_header("X-Stripe-Client-User-Agent")
)
assert "source" not in client_ua

def test_uses_given_idempotency_key(self, requestor, http_client_mock):
method = "post"
http_client_mock.stub_request(
Expand Down
Loading