Refuse OPDS logins when CWA accepts every password
A CWA with anonymous browsing can answer an OPDS request whatever the password, so each password logged a user in to Bindery. Bindery now tests CWA with a login that cannot exist, and refuses OPDS logins if CWA accepts it. A login check no longer shares the session of the account of Bindery. The CWA account stays optional for a CWA that asks for no login, and a missing account now gets its own message.
This commit is contained in:
+3
-1
@@ -26,6 +26,8 @@ class FakeCwa:
|
||||
self.requests: list[str] = []
|
||||
# CWA's "Show Publisher Section" setting. Without it, the publisher feed is 404.
|
||||
self.publisher_view = True
|
||||
# CWA's anonymous browsing. With it, the fake answers every request, with or without a login.
|
||||
self.anonymous = False
|
||||
fake = self
|
||||
|
||||
class Handler(BaseHTTPRequestHandler):
|
||||
@@ -87,7 +89,7 @@ class FakeCwa:
|
||||
self._send(h, 200, self._feed([self._book_entry(b) for b in page], next_href), "application/atom+xml")
|
||||
|
||||
def handle(self, h) -> None:
|
||||
if not self._authorized(h):
|
||||
if not self.anonymous and not self._authorized(h):
|
||||
self._send(h, 401, b"Unauthorized", "text/plain")
|
||||
return
|
||||
url = urlparse(h.path)
|
||||
|
||||
@@ -81,6 +81,20 @@ def test_cwa_login(client, cwa):
|
||||
assert client.get("/api/stories").status_code == 401
|
||||
|
||||
|
||||
def test_cwa_that_accepts_every_login_cannot_check_a_password(client, cwa, caplog):
|
||||
cwa.anonymous = True
|
||||
for password in ("pw", "wrong"):
|
||||
response = login(client, password=password)
|
||||
assert response.status_code == 401
|
||||
assert response.json["error"] == "Cannot check the login with CWA. The log has the details."
|
||||
assert "CWA accepts every OPDS login" in caplog.text
|
||||
assert client.get("/api/stories").status_code == 401
|
||||
# These are not wrong passwords, so they lock no one out.
|
||||
cwa.anonymous = False
|
||||
orchestrator.cwa()._login_test = None
|
||||
assert login(client).status_code == 200
|
||||
|
||||
|
||||
def test_a_session_ends_after_one_day(client, cwa, monkeypatch):
|
||||
response = client.post(
|
||||
"/api/auth/login", json={"username": "reader", "password": "pw", "remember": True}, headers=H
|
||||
|
||||
@@ -53,6 +53,7 @@ def db_login(client, user_db, monkeypatch):
|
||||
monkeypatch.setattr(config, "CWA_DB_PATH", user_db)
|
||||
remote = Mock(spec=CwaClient)
|
||||
remote.login_ok.return_value = True
|
||||
remote.accepts_any_login.return_value = False
|
||||
monkeypatch.setattr(orchestrator, "cwa", lambda: remote)
|
||||
return remote
|
||||
|
||||
|
||||
+32
-1
@@ -1,13 +1,15 @@
|
||||
"""The OPDS client, the book finder, the login check, the ingest folder and the epub reader."""
|
||||
|
||||
import json
|
||||
import time
|
||||
from unittest.mock import Mock
|
||||
|
||||
import pytest
|
||||
from fake_cwa import FakeBook, FakeCwa
|
||||
from test_build import URL, chapters, metahtml
|
||||
|
||||
from bindery import fff
|
||||
from bindery.cwa import BookFinder, CwaClient, CwaError, plain_title, search_text, title_rank
|
||||
from bindery.cwa import LOGIN_TEST_SECONDS, BookFinder, CwaClient, CwaError, plain_title, search_text, title_rank
|
||||
from bindery.epub import epub_metadata, epub_urls
|
||||
from bindery.ingest import MAX_NAME_LENGTH, IngestError, IngestFolder
|
||||
|
||||
@@ -63,6 +65,35 @@ def test_wrong_password_and_missing_address(fake):
|
||||
CwaClient("").search("x")
|
||||
|
||||
|
||||
def test_cwa_that_needs_no_login_works_without_an_account(fake):
|
||||
fake.anonymous = True
|
||||
open_client = CwaClient(fake.url)
|
||||
assert [b.id for b in open_client.search("Another")] == [3]
|
||||
assert open_client.get_book("uuid-2")["id"] == 2
|
||||
fake.anonymous = False
|
||||
with pytest.raises(CwaError, match="CWA asks for a login. Set CWA_USERNAME and CWA_PASSWORD."):
|
||||
open_client.search("Test Story")
|
||||
|
||||
|
||||
def test_a_login_check_does_not_use_the_session_of_the_account(fake, monkeypatch):
|
||||
c = client(fake)
|
||||
monkeypatch.setattr(c.session, "get", Mock(side_effect=AssertionError("The login check used the session")))
|
||||
assert c.login_ok("bindery", "secret")
|
||||
assert not c.login_ok("bindery", "wrong")
|
||||
|
||||
|
||||
def test_the_test_of_the_logins_of_cwa_is_kept_for_a_time(fake, monkeypatch):
|
||||
c = client(fake)
|
||||
assert not c.accepts_any_login()
|
||||
fake.anonymous = True
|
||||
assert not c.accepts_any_login() # the last test still counts
|
||||
assert len([r for r in fake.requests if r == "/opds"]) == 1
|
||||
clock = time.monotonic()
|
||||
monkeypatch.setattr(time, "monotonic", lambda: clock + LOGIN_TEST_SECONDS + 1)
|
||||
assert c.accepts_any_login()
|
||||
assert CwaClient(fake.url).accepts_any_login()
|
||||
|
||||
|
||||
def test_get_book(fake):
|
||||
c = client(fake)
|
||||
assert c.get_book("uuid-2") == {
|
||||
|
||||
Reference in New Issue
Block a user