feat(oauth)!: fallback discovery na /o/*, tekstowy tryb logowania, wymagany BPP_BASE_URL#5
Merged
Conversation
…klejka Dwie przeszkody wyszly przy podlaczaniu instancji publikacje.up.lublin.pl. 1) Discovery (RFC 8414) padalo na 403. Brzegowy nginx blokuje tam caly /.well-known/ regula na pliki ukryte (`location ~ /\.`), mimo ze serwer autoryzacji dziala bez zarzutu: /o/authorize/ -> 302, /o/token/ -> 400, /o/register/ -> 201 z poprawnym client_id. discover() wywracalo logowanie na pierwszym kroku, choc bylo w pelni wykonalne. discover() cofa sie teraz na konwencjonalne sciezki django-oauth-toolkit na TYM SAMYM hoscie. Fallback nie zgaduje hosta ani sekretow, a prawidlowe metadane maja pierwszenstwo — instancja z serwerem autoryzacji pod innym adresem nie zostanie nadpisana (osobny test). 2) Logowanie zakladalo, ze przegladarka i bpp-mcp stoja na tej samej maszynie. Przy pracy zdalnej callback na 127.0.0.1 nigdy nie wraca. login() wypisuje teraz adres autoryzacji takze tekstem i rownolegle z loopbackiem przyjmuje wklejke uzytkownika (pelny adres przekierowania albo sam parametr code). Obie drogi celuja w ta sama kolejke — wygrywa szybsza, wiec nie ma trybow do wyboru. Kontrola state pozostaje twarda na loopbacku. Rozluzniona jest WYLACZNIE dla recznie wklejonego golego kodu (znacznik _recznie), ktory z natury nie niesie state. Wklejka z niezgodnym state jest odrzucana — osobny test, zeby wklejka nie stala sie furtka omijajaca CSRF-ochrone. Zweryfikowane na zywej instancji: discover() zwraca poprawna trojke endpointow mimo 403 na /.well-known/. 152 testy zielone. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019D6Zytbsj8xjZCx8etyMeN
…z sieci
Poprzedni commit obiecywal, ze kontrola state pozostaje twarda na loopbacku.
Nie byla. Znacznik `_recznie` siedzial w tym samym slowniku parametrow, ktory
_CallbackHandler buduje z query stringa przyjetego na loopbacku, a login()
czytal go wlasnie stamtad. Zadanie:
GET http://127.0.0.1:<port>/callback?code=PODSZYTY&_recznie=1
dawalo params bez `state` i z ustawionym znacznikiem, wiec warunek
`not (params.get("_recznie") and otrzymany_state is None)` wychodzil falszem
i kod przechodzil bez zadnej weryfikacji state. Port jest efemeryczny, ale
skanowalny ze zlosliwej strony w przegladarce — to dokladnie ten scenariusz
CSRF, przed ktorym state ma chronic (RFC 8252).
Naprawa: `_parsuj_wklejone` zwraca teraz `(params, goly_kod)`, a kolejka
niesie krotke. Loopback wstawia `False` na sztywno, wiec zrodlo znacznika jest
strukturalnie niedostepne dla sieci — nie da sie go podszyc zadnym parametrem.
Rozluznienie state dotyczy wylacznie tekstu wklejonego na stdin.
Test regresyjny odtwarza atak (callback z `_recznie=1` bez `state`) i wymaga
odrzucenia; przed poprawka pada, po poprawce 153/153 zielone.
Znalezione przez automatyczny przeglad bezpieczenstwa.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019D6Zytbsj8xjZCx8etyMeN
… umlub Domyslny host wskazywal na https://bpp.umlub.pl. Serwer jest z zalozenia wielo-instancyjny, wiec kazdy zaszyty host faworyzuje jedna uczelnie, ale gorszy jest tryb awarii: uzytkownik, ktory zapomni ustawic BPP_BASE_URL, NIE dostaje bledu — dostaje kompletna, poprawnie wygladajaca bibliografie CUDZEJ instytucji. Blad cichy i trudny do zauwazenia po wynikach. Doszlo do tego, ze domyslna instancja byla przestarzala: bpp.umlub.pl nie ma Fazy 0 (brak /api/v1/szukaj/), nie ma /o/ ani /api/v1/whoami/. Czyli domyslna konfiguracja celowala w serwer, na ktorym polowa narzedzi nie dziala. Config.from_env() podnosi teraz BrakKonfiguracji z komunikatem mowiacym, co ustawic. Pusty/bialy string traktowany jak brak. BREAKING CHANGE: konfiguracje bez BPP_BASE_URL przestaja startowac. Swiadomie — glosna odmowa jest lepsza niz ciche odpytywanie nie tej bazy, co trzeba. Blad wychodzi na stderr z instrukcja i exit 2, bez tracebacku; `--help`, `login` i `logout` dzialaja normalnie po ustawieniu hosta, a `--help` takze bez niego (parse_args idzie przed odczytem konfiguracji). Modulowy `mcp` w server.py budowal sie przy imporcie, wiec brak zmiennej wywracalby import tracebackiem zanim main() zdazy cokolwiek wypisac — stad opakowanie w try i `mcp = None`, a czytelny komunikat wychodzi z main(). conftest ustawia host przed importem modulow testowych (fixtury sa na to za pozno — server buduje sie na etapie kolekcji). README bez zadnej konkretnej uczelni: wszystkie przyklady na bpp.twoja-uczelnia.pl. 157 testow zielonych. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019D6Zytbsj8xjZCx8etyMeN
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Wyszło przy podłączaniu instancji z nowym API (
publikacje.up.lublin.pl).Warstwa anonimowa działała od ręki —
szukaj_publikacji,szukaj_autora,slownikodpowiadały poprawnie. Zablokowana była wyłącznie autoryzacja, i toz trzech niezależnych powodów.
1. Discovery padało na 403 mimo sprawnego OAuth
discover()czyta metadane RFC 8414 spod/.well-known/oauth-authorization-server. Część wdrożeń blokuje na brzegu cały/.well-known/regułą nginksa na pliki ukryte (location ~ /\.) i oddaje 403 —podczas gdy serwer autoryzacji jest w pełni sprawny:
/o/authorize//accounts/login//o/token//o/register/(DCR)client_id/.well-known/oauth-authorization-serverraise_for_status()wywracało logowanie na pierwszym kroku, więc komunikatbrzmiał „nie da się zalogować", choć logowanie było w pełni wykonalne.
discover()cofa się teraz na konwencjonalne ścieżki django-oauth-toolkit natym samym hoście. Fallback nie zgaduje hosta ani sekretów, a prawidłowe
metadane zawsze mają pierwszeństwo — instancja z serwerem autoryzacji pod
innym adresem nie zostanie nadpisana (osobny test). Fallback obejmuje 403, 404,
200-z-HTML-em, JSON bez wymaganych pól i błąd sieci.
Naprawa u źródła jest po stronie deploymentu:
iplweb/bpp-deploy#21. Fallback
zostaje jako odporność klienta — ale nie naprawia natywnego przycisku
„authorize" w trybie HTTP, bo tam discovery robi sam klient Claude.
2. Logowanie zakładało przeglądarkę na tej samej maszynie
Przy pracy zdalnej (SSH, serwer bez GUI) callback na
127.0.0.1nigdy niewraca — przeglądarka stoi gdzie indziej.
login()wypisuje teraz adres autoryzacji także tekstem i równolegle zloopbackiem przyjmuje wklejkę użytkownika: pełny adres przekierowania albo sam
parametr
code. Obie drogi celują w tę samą kolejkę, więc nie ma trybów dowyboru — automat działa, kiedy może, wklejka ratuje, kiedy nie może.
3. Domyślny
BPP_BASE_URLcicho kierował na cudzą uczelnięBREAKING CHANGE.
Domyślny host to było
https://bpp.umlub.pl. Serwer jest z założeniawielo-instancyjny, więc każdy zaszyty host faworyzuje jedną uczelnię — ale
gorszy jest tryb awarii: użytkownik, który zapomni ustawić
BPP_BASE_URL, niedostaje błędu, tylko kompletną, poprawnie wyglądającą bibliografię cudzej
instytucji. Błąd cichy i niewykrywalny po samych wynikach.
Do tego domyślna instancja była przestarzała:
bpp.umlub.plnie ma Fazy 0(brak
/api/v1/szukaj/), nie ma/o/ani/api/v1/whoami/. Domyślnakonfiguracja celowała więc w serwer, na którym połowa narzędzi nie działa.
Config.from_env()podnosi terazBrakKonfiguracjiz komunikatem mówiącym, coustawić (exit 2, bez tracebacku).
--helpdziała bez konfiguracji. README niewymienia już żadnej konkretnej uczelni.
Bezpieczeństwo
Rozluźnienie kontroli
state(potrzebne przy wklejeniu samego kodu, którystatenie niesie) było w pierwszym podejściu omijalne z sieci — znacznik_reczniesiedział w tym samym słowniku parametrów, który handler loopbackubuduje z query stringa. Żądanie:
przechodziło bez żadnej weryfikacji
state. Port jest efemeryczny, aleskanowalny ze złośliwej strony — dokładnie ten scenariusz CSRF, przed którym
statema chronić (RFC 8252).Naprawione osobnym commitem:
_parsuj_wklejonezwraca(params, goly_kod),kolejka niesie krotkę, a loopback wstawia
Falsena sztywno — źródło znacznikajest strukturalnie niedostępne dla sieci. Rozluźnienie dotyczy wyłącznie tekstu
wklejonego na stdin. Test regresyjny odtwarza atak i wymaga odrzucenia.
Weryfikacja
discover()na żywej instancji zwraca poprawną trójkę endpointów mimo 403.bpp-mcp login→token w store →
whoami200 →zapytanie_rekord("rok >= 2024 and impact_factor > 5")→ 318 trafień.BPP_BASE_URL: czytelny komunikat + exit 2;--helpdziała.