Skip to content
Open
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Formularz „Zgłoś publikację" znów podpowiada jednostkę i dyscyplinę autora
zalogowanym użytkownikom bez uprawnień redaktorskich. Endpoint, z którego
korzysta ta podpowiedź — tylko czytający dane, niczego nie zapisujący —
trafił przez pomyłkę pod bramkę uprawnień redaktorskich, więc taki
użytkownik dostawał w tle błąd i tracił podpowiedź (po cichu, bo to
zapytanie w tle). Dla niezalogowanych podpowiedź nadal nie działa — tak jak
wcześniej.
15 changes: 13 additions & 2 deletions src/bpp/tests/test_authz_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,16 +64,27 @@ def test_toz_anonim_przekierowuje(client):

# --- API punktacji / habilitacji / jednostki -----------------------------

API_ROUTES = [
# Endpointy REDAKCYJNE: zalogowany bez uprawnień do wprowadzania danych → 403.
API_ROUTES_REDAKCYJNE = [
("bpp:api_rok_habilitacji", {}),
("bpp:api_punktacja_zrodla", {"zrodlo_id": 1, "rok": 2020}),
("bpp:api_upload_punktacja_zrodla", {"zrodlo_id": 1, "rok": 2020}),
]

# Wszystkie endpointy API wymagające ZALOGOWANIA (anonim → 302 na login).
# `api_ostatnia_jednostka_i_dyscyplina` jest tutaj, ale ŚWIADOMIE nie ma go na
# liście redakcyjnej wyżej: to widok tylko-do-odczytu, konsumowany przez
# `autorform_dependant.js` w PUBLICZNYM formularzu `zglos_publikacje`, więc
# zwykły zalogowany użytkownik musi dostać 200, a nie 403. Pilnuje tego
# `test_ostatnia_jednostka_dostepna_bez_uprawnien_redaktorskich`
# w `src/bpp/tests/test_views/test_api.py`.
API_ROUTES = API_ROUTES_REDAKCYJNE + [
("bpp:api_ostatnia_jednostka_i_dyscyplina", {}),
]


@pytest.mark.django_db
@pytest.mark.parametrize("name,kwargs", API_ROUTES)
@pytest.mark.parametrize("name,kwargs", API_ROUTES_REDAKCYJNE)
def test_api_zwykly_user_403(client, zwykly_user, name, kwargs):
client.force_login(zwykly_user)
url = reverse(name, kwargs=kwargs)
Expand Down
63 changes: 63 additions & 0 deletions src/bpp/tests/test_views/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@

import pytest
from django.urls import reverse
from model_bakery import baker

from bpp.models import Autor_Dyscyplina, Typ_Odpowiedzialnosci
from bpp.models.zrodlo import Punktacja_Zrodla
from bpp.permissions import moze_wprowadzac_dane
from bpp.tests.util import CURRENT_YEAR, any_autor, any_habilitacja, any_zrodlo
from bpp.views.api import (
OstatniaJednostkaIDyscyplinaView,
Expand Down Expand Up @@ -420,6 +422,67 @@ def test_api_endpoints_require_login(client, url_name, url_kwargs, post_data):
)


@pytest.mark.django_db
def test_ostatnia_jednostka_dostepna_bez_uprawnien_redaktorskich(
client, autor, jednostka
):
"""Podpowiadanie jednostki działa dla ZALOGOWANEGO usera bez uprawnień
redaktorskich.

Regresja (Rollbar #1532 i ~19 bliźniaczych itemów): endpoint dostał
``WprowadzanieDanychRequiredMixin``, choć niczego nie mutuje — tylko czyta.
Konsumuje go ``autorform_dependant.js`` ładowany do PUBLICZNEGO formularza
``zglos_publikacje``, więc każdy zgłaszający bez roli redaktora dostawał
403 i tracił podpowiedź jednostki (po cichu — to AJAX).
"""
jednostka.dodaj_autora(autor)

user = baker.make("bpp.BppUser", is_staff=False, is_superuser=False)
assert not moze_wprowadzac_dane(user)
client.force_login(user)

url = reverse("bpp:api_ostatnia_jednostka_i_dyscyplina")
response = client.post(url, data={"autor_id": autor.pk, "rok": CURRENT_YEAR})

assert response.status_code == 200
assert json.loads(response.content)["jednostka_id"] == jednostka.pk


@pytest.mark.django_db
@pytest.mark.parametrize(
"url_name,url_kwargs,post_data",
[
("bpp:api_rok_habilitacji", {}, {"autor_pk": 1}),
("bpp:api_punktacja_zrodla", {"zrodlo_id": 1, "rok": CURRENT_YEAR}, {}),
(
"bpp:api_upload_punktacja_zrodla",
{"zrodlo_id": 1, "rok": CURRENT_YEAR},
{"impact_factor": "50.0"},
),
],
)
def test_pozostale_api_nadal_wymagaja_uprawnien_redaktorskich(
client, url_name, url_kwargs, post_data
):
"""Lustro poprzedniego testu: poluzowanie dotyczy JEDNEGO widoku.

Bez tego nic nie broni przed przyszłym „skoro tamten odblokowaliśmy, to
odblokujmy wszystkie" — a te trzy albo mutują dane
(``UploadPunktacjaZrodlaView``), albo wystawiają dane redakcyjne
konsumowane wyłącznie przez JS admina.
"""
user = baker.make("bpp.BppUser", is_staff=False, is_superuser=False)
assert not moze_wprowadzac_dane(user)
client.force_login(user)

response = client.post(reverse(url_name, kwargs=url_kwargs), data=post_data)

assert response.status_code == 403, (
f"{url_name} przepuszcza zalogowanego bez uprawnień redaktorskich "
f"(status={response.status_code})"
)


@pytest.mark.django_db
def test_upload_punktacja_zrodla_anon_does_not_write(client):
"""Najtwardszy regression test: anonim NIE może utworzyć Punktacja_Zrodla."""
Expand Down
16 changes: 15 additions & 1 deletion src/bpp/views/api/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from decimal import Decimal, InvalidOperation

from django.contrib.auth.mixins import LoginRequiredMixin
from django.db import models, transaction
from django.http import JsonResponse
from django.http.response import HttpResponseNotFound
Expand Down Expand Up @@ -154,9 +155,22 @@ def ostatnia_dyscyplina(request, a, rok):
return ad.dyscyplina_naukowa or ad.subdyscyplina_naukowa


class OstatniaJednostkaIDyscyplinaView(WprowadzanieDanychRequiredMixin, View):
class OstatniaJednostkaIDyscyplinaView(LoginRequiredMixin, View):
"""Zwraca jako JSON ostatnią jednostkę danego autora oraz ewentualnie jego
dyscyplinę naukową, w sytuacji gdy jest ona jedna i określona na dany rok.

ŚWIADOMIE ``LoginRequiredMixin``, a NIE
``WprowadzanieDanychRequiredMixin``: widok niczego nie mutuje — czyta
``Autor``/``Autor_Dyscyplina`` i zwraca JSON. Konsumuje go
``autorform_dependant.js``, ładowany także do PUBLICZNEGO formularza
``zglos_publikacje`` (patrz ``zglos_publikacje.forms``), więc bramka
redaktorska odcinała zwykłych zgłaszających od podpowiedzi jednostki
i dyscypliny — po cichu, bo to AJAX.

Anonim NADAL dostaje 302 na login (kontrakt ``LoginRequiredMixin``), więc
dla niezalogowanych zgłaszających podpowiedź nie działa — tak samo jak
przed ``e892142ff``. Poluzowanie tego to osobna decyzja: endpoint jest
oraklem istnienia autora dla całej przestrzeni PK i nie ma rate-limitu.
"""

def post(self, request, *args, **kw):
Expand Down