Skip to content
Merged
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
40 changes: 28 additions & 12 deletions src/epint/modules/http_client/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -158,22 +158,31 @@ def _check_rate_limit(self, response: Response) -> Optional[float]:

def _is_tgt_invalid(self, response: Response) -> bool:
"""
404 hatasında TGT geçersizliğini kontrol et
404 (CAS: "TGT ... could not be found/is considered invalid") veya 401
(Şeffaflık/EPYS servisleri: errorCode AUTH009 "Güvenlik bilgisi(TGT)
hatalı!") hatasında TGT geçersizliğini kontrol et.

Args:
response: HTTP response objesi

Returns:
TGT geçersizse True, değilse False
"""
if response.status_code != 404:
if response.status_code not in (401, 404):
return False

try:
# Response body'yi text olarak al
response_text = response.text if hasattr(response, 'text') else str(response.content or '')
response_text_lower = response_text.lower()

if response.status_code == 401:
# Servis 401 döndüğünde body'de errorCode: AUTH009 var mı kontrol et
# (bkz. şeffaflık örneği: {"errorCode": "AUTH009", "errorMessage":
# "Güvenlik bilgisi(TGT) hatalı!"}) - bu, kullanılan TGT'nin servis
# tarafında (halihazırda) geçersiz sayıldığı anlamına gelir.
return 'auth009' in response_text_lower

# TGT geçersizliğini belirten anahtar kelimeler
tgt_invalid_keywords = [
'could not be found',
Expand Down Expand Up @@ -247,24 +256,31 @@ def _make_request(
try:
response = session.request(method=method.upper(), url=url, **kwargs)

# 404 hatası ve TGT geçersizliği kontrolü
if response.status_code == 404 and self._is_tgt_invalid(response):
# 404 (CAS) veya 401 AUTH009 (şeffaflık/EPYS) - TGT geçersizliği kontrolü
if response.status_code in (401, 404) and self._is_tgt_invalid(response):
if effective_auth and tgt_retry_count < max_tgt_retries:
# TGT geçersiz, ticket'ları temizle
logger.warning("TGT geçersiz, ticket cache temizleniyor ve yenileniyor (url=%s)", url)
effective_auth.clear_tickets()

# URL'de TGT kodu varsa yeni TGT ile güncelle
if '/cas/v1/tickets/' in url or '/v1/tickets/' in url:
try:
# Yeni TGT al
new_tgt_code, _ = effective_auth.get_tgt()
# URL'deki eski TGT kodunu yeni ile değiştir
try:
# Yeni TGT al
new_tgt_code, _ = effective_auth.get_tgt()

# URL'de TGT kodu varsa yeni TGT ile güncelle (CAS ST üretimi)
if '/cas/v1/tickets/' in url or '/v1/tickets/' in url:
import re
# TGT- ile başlayan kodu bul ve değiştir
url = re.sub(r'TGT-[^/]+', new_tgt_code, url)
except Exception:
pass

# Header'da TGT varsa yeni TGT ile güncelle (şeffaflık
# injection-quantity gibi servisler TGT'yi 'TGT' header'ında
# gönderir, URL'de değil - bkz. AUTH009 örneği)
request_headers = kwargs.get('headers')
if request_headers and 'TGT' in request_headers:
request_headers['TGT'] = new_tgt_code
except Exception:
pass

tgt_retry_count += 1
retry_count += 1
Expand Down
29 changes: 29 additions & 0 deletions tests/test_http_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,35 @@ def test_shared_client_session_persists_across_requests(monkeypatch):
assert client._session is not None


def test_401_auth009_clears_tickets_and_refreshes_tgt_header(monkeypatch):
# Regresyon testi: şeffaflık servisleri TGT geçersizliğini 404 değil,
# 401 + {"errorCode": "AUTH009"} ile bildiriyor (bkz. _is_tgt_invalid).
# Bu durumda eskiden hiç fark edilmeyip aynı bayat TGT ile tekrar
# denenip nihayetinde exception fırlatılıyordu; artık ticket cache
# temizlenip yeni TGT alınmalı ve 'TGT' header'ı güncellenmeli.
client = HTTPClient()

unauthorized = MagicMock(status_code=401, headers={})
unauthorized.text = '{"errorCode": "AUTH009", "errorMessage": "Güvenlik bilgisi(TGT) hatalı!"}'
ok = MagicMock(status_code=200, headers={})
ok.raise_for_status = lambda: None
responses = [unauthorized, ok]

session = MagicMock(request=lambda **kw: responses.pop(0))
monkeypatch.setattr(client, "_get_session", lambda: session)

auth = MagicMock()
auth.get_tgt.return_value = ("TGT-NEW", "2099-01-01 00:00:00")
headers = {"TGT": "TGT-STALE"}

result = client.post("https://seffaflik.epias.com.tr/x", auth=auth, headers=headers)

assert result is ok
auth.clear_tickets.assert_called_once()
auth.get_tgt.assert_called_once()
assert headers["TGT"] == "TGT-NEW"


def test_make_request_uses_per_call_auth_not_instance_state(monkeypatch):
# Regresyon testi: auth artık client'a instance state olarak yazılmıyor
# (paylaşılan client'ta bu, eşzamanlı farklı auth'lu çağrılar arasında
Expand Down
Loading