diff --git a/README.md b/README.md index 56f6d0c..7a48741 100644 --- a/README.md +++ b/README.md @@ -58,12 +58,14 @@ Tenslotte, via de commandline kan je de module ook als volgt aanroepen: ## Opmerkingen - **Authenticatie**. Inloggen bij de bibliotheek.be website gebeurt standaard - via een webformulier. Het is ook mogelijk om de snellere `oauth` manier te - gebruiken; dit is nog experimenteel. + via een webformulier. Het is ook mogelijk om de `oauth` manier te gebruiken; + maar dit is nog experimenteel. mb = MijnBibliotheek(username, password, login_by="oauth") accounts = mb.get_accounts() + ! Opmerking: De oauth flow is sinds maart 2024 broken, en vereist nog een aanpassing. + - **Foutafhandeling**. Afhankelijk van de toepassing, kan het aangeraden zijn om foutafhandeling te voorzien. Het bestand `errors.py` bevat de lijst van Mijnbib-specifieke exceptions. De docstrings van de publieke methods bevatten diff --git a/changelog.md b/changelog.md index d7a58a5..fc72f3b 100644 --- a/changelog.md +++ b/changelog.md @@ -2,6 +2,12 @@ new: new feature / impr: improvement / fix: bug fix +## v0.5.5 - 2024-03-05 + +- fix: broken login (form) because of change at site. + Note: alternative oauth login still broken. +- impr: raise TemporarySiteError at oauth login when 5xx (part 3) + ## v0.5.4 - 2024-02-21 - impr: improve extensibility of oauth login handler diff --git a/pyproject.toml b/pyproject.toml index 6526e7c..5bc2857 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "mijnbib" -version = "0.5.4" +version = "0.5.5" description = "Python API voor de website mijn.bibliotheek.be" readme = "README.md" authors = [{ name = "Ward Van Heddeghem", email = "wardvh@fastmail.fm" }] diff --git a/src/mijnbib/login_handlers.py b/src/mijnbib/login_handlers.py index bbb5d73..da4f26a 100644 --- a/src/mijnbib/login_handlers.py +++ b/src/mijnbib/login_handlers.py @@ -45,6 +45,12 @@ def _log_in(self): _log.debug("Opening login page ... ") response = self._br.open(self._url, timeout=TIMEOUT) html_string_start_page = response.read().decode("utf-8") # type:ignore + # Workaround for mechanize.BrowserStateError: not viewing HTML + # because suddenly (March 2024) Content-Type header is "application/octet-stream;charset=UTF-8" + # which is not recognized as html by mechanize + # Alternative is to configure the browser instance with + # self._br.set_header("Accept", "text/html") + self._br._factory.is_html = True self._br.select_form(nr=0) self._br["email"] = self._username self._br["password"] = self._pwd @@ -100,6 +106,7 @@ def _log_in(self): # GET https://gent.bibliotheek.be/mijn-bibliotheek/aanmelden # example response: # header Location: https://mijn.bibliotheek.be/openbibid/rest/auth/authorize?hint=login&oauth_callback=https://gent.bibliotheek.be/my-library/login/callback&oauth_token=5abee3c0f5c04beead64d8e625ead0e7&uilang=nl + _log.debug("----") response = self._s.get(self._url, allow_redirects=False) _log.debug(f"login (1) status code : {response.status_code}") _log.debug(f"login (1) headers : {response.headers}") @@ -125,9 +132,10 @@ def _log_in(self): ) if "/mijn-bibliotheek/overzicht" in oauth_location_url: _log.info("Already authenticated. No need to log in again.") - return + return response # better for extensibility (i.e. sOlid) # (2) Authorize based on Location url (get session id) + _log.debug("----") response = self._s.get(oauth_location_url, allow_redirects=False) _log.debug(f"login (2) status code : {response.status_code}") _log.debug(f"login (2) headers : {response.headers}") @@ -149,6 +157,7 @@ def _log_in(self): "email": self._username, "password": self._pwd, } + _log.debug("----") response = self._s.post(url, data=data, allow_redirects=False) _log.debug(f"login (3) status code : {response.status_code}") _log.debug(f"login (3) headers : {response.headers}") @@ -164,6 +173,10 @@ def _log_in(self): _log.debug(f"login (3) hint : {hint}") if response.status_code == 200: raise AuthenticationError("Login not accepted. Correct credentials?") + if response.status_code >= 500: # we've observed 500 + raise TemporarySiteError( + f"Expected status code 303 during log in. Got '{response.status_code}'" + ) if response.status_code != 303: raise IncompatibleSourceError( f"Expected status code 303 during log in. Got '{response.status_code}'", @@ -171,6 +184,7 @@ def _log_in(self): ) # (4) Call login callback based on Location url + _log.debug("----") response = self._s.get(login_location_url, allow_redirects=False) _log.debug(f"login (4) status code : {response.status_code}") _log.debug(f"login (4) headers : {response.headers}") diff --git a/tests/test_mijnbibliotheek.py b/tests/test_mijnbibliotheek.py index 588d084..5fd0304 100644 --- a/tests/test_mijnbibliotheek.py +++ b/tests/test_mijnbibliotheek.py @@ -19,9 +19,16 @@ def creds_config(scope="module"): yield dict(**config.defaults()) +class X(str): + pass + + class FakeMechanizeBrowser: def __init__(self, form_response: str) -> None: self._form_response = form_response.encode("utf8") + # trick for nested prop, from https://stackoverflow.com/a/35190607/50899 + self._factory = X("_factory") + self._factory.is_html = None # can be whatever def __setitem__(self, key, value): pass