Skip to content

Commit a208268

Browse files
committed
Fix: removing stale client site session cookie before relogin
1 parent 0519698 commit a208268

2 files changed

Lines changed: 97 additions & 0 deletions

File tree

src/osw/wtsite.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,11 @@ def _relogin(self):
171171
)
172172
)
173173
if isinstance(cred, CredentialManager.UserPwdCredential):
174+
# Stale session cookies cause MediaWiki to abort the login flow with
175+
# "Unable to continue login. Your session most likely timed out."
176+
# Clear client-side session state so login starts from a clean slate.
177+
self._site.connection.cookies.clear()
178+
self._site.tokens.clear()
174179
self._site.login(username=cred.username, password=cred.password)
175180
else:
176181
raise RuntimeError(

tests/integration/test_wtsite.py

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -329,6 +329,98 @@ def test_relogin_calls_site_login_with_stored_credentials(self, mocker):
329329
)
330330

331331

332+
class TestReloginClearsStaleSessionState:
333+
"""Regression test: _relogin() must clear stale cookies and cached tokens
334+
before calling login().
335+
336+
Without this, MediaWiki's API returns result='Aborted' with reason
337+
"Unable to continue login. Your session most likely timed out." when the
338+
server-side session has expired but the cookie jar still holds the old
339+
session cookie.
340+
"""
341+
342+
def test_relogin_clears_cookies_and_tokens_before_login(self, mocker):
343+
mock_mw_site = MagicMock()
344+
mock_cred_mngr = MagicMock(spec=CredentialManager)
345+
mock_cred = MagicMock(spec=CredentialManager.UserPwdCredential)
346+
mock_cred.username = "TestUser"
347+
mock_cred.password = "TestPass"
348+
mock_cred_mngr.get_credential.return_value = mock_cred
349+
350+
wt_site = WtSite.__new__(WtSite)
351+
wt_site._cred_mngr = mock_cred_mngr
352+
wt_site._iri = "stale-session.example.com"
353+
wt_site._site = mock_mw_site
354+
wt_site._page_cache = {}
355+
wt_site._cache_enabled = False
356+
357+
call_order = []
358+
mock_mw_site.connection.cookies.clear.side_effect = lambda: call_order.append(
359+
"cookies.clear"
360+
)
361+
mock_mw_site.tokens.clear.side_effect = lambda: call_order.append(
362+
"tokens.clear"
363+
)
364+
mock_mw_site.login.side_effect = lambda **_: call_order.append("login")
365+
366+
wt_site._relogin()
367+
368+
mock_mw_site.connection.cookies.clear.assert_called_once_with()
369+
mock_mw_site.tokens.clear.assert_called_once_with()
370+
mock_mw_site.login.assert_called_once_with(
371+
username="TestUser", password="TestPass"
372+
)
373+
# Cookies and tokens must be cleared BEFORE login is attempted.
374+
assert call_order.index("cookies.clear") < call_order.index("login")
375+
assert call_order.index("tokens.clear") < call_order.index("login")
376+
377+
def test_relogin_recovers_when_server_aborts_login_with_stale_cookies(self, mocker):
378+
"""Reproduces the production failure: server returns 'Aborted' on the
379+
first login call because stale cookies are still present, but succeeds
380+
once client-side state has been cleared.
381+
"""
382+
mock_mw_site = MagicMock()
383+
mock_cred_mngr = MagicMock(spec=CredentialManager)
384+
mock_cred = MagicMock(spec=CredentialManager.UserPwdCredential)
385+
mock_cred.username = "TestUser"
386+
mock_cred.password = "TestPass"
387+
mock_cred_mngr.get_credential.return_value = mock_cred
388+
389+
# Simulate a cookie jar that still holds a stale session cookie.
390+
fake_cookies = {"osl_session": "stale-id"}
391+
392+
def fake_clear():
393+
fake_cookies.clear()
394+
395+
mock_mw_site.connection.cookies.clear.side_effect = fake_clear
396+
397+
# login() fails with the exact MediaWiki LoginError seen in production
398+
# if cookies are still present when called; succeeds otherwise.
399+
def fake_login(**_):
400+
if fake_cookies:
401+
raise mwclient.errors.LoginError(
402+
mock_mw_site,
403+
"Aborted",
404+
"Unable to continue login. Your session most likely " "timed out.",
405+
)
406+
407+
mock_mw_site.login.side_effect = fake_login
408+
409+
wt_site = WtSite.__new__(WtSite)
410+
wt_site._cred_mngr = mock_cred_mngr
411+
wt_site._iri = "stale-session.example.com"
412+
wt_site._site = mock_mw_site
413+
wt_site._page_cache = {}
414+
wt_site._cache_enabled = False
415+
416+
# Should not raise: _relogin clears the stale cookie before calling login
417+
wt_site._relogin()
418+
419+
mock_mw_site.login.assert_called_once_with(
420+
username="TestUser", password="TestPass"
421+
)
422+
423+
332424
# -- Live integration tests (require wiki credentials) --------------------
333425

334426

0 commit comments

Comments
 (0)