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
2 changes: 1 addition & 1 deletion docker/services/uwsgi/uwsgi.d/40_log.ini
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
[uwsgi]
log-format = [uwsgi] %(var.HTTP_X_FORWARDED_FOR) (%(addr)) - - [%(ltime)] "%(method) %(uri) %(proto)" %(status) %(size) "%(referer)" "%(uagent)" host_hdr=%(host) req_time_elapsed=%(msecs) process=%(pid) worker=%(wid)
log-format = [uwsgi] %(var.HTTP_X_FORWARDED_FOR) (%(addr)) %(user) - [%(ltime)] "%(method) %(uri) %(proto)" %(status) %(size) "%(referer)" "%(uagent)" host_hdr=%(host) req_time_elapsed=%(msecs) process=%(pid) worker=%(wid)
logger = stdio:
logger = file:/var/log/uwsgi/uwsgi.log
13 changes: 13 additions & 0 deletions src/palace/manager/api/controller/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,10 +65,23 @@ def authenticated_patron_from_request(self) -> ProblemDetail | Patron | Response
# No credentials were provided.
return self.authenticate()

# Set REMOTE_USER for uwsgi access log visibility. We set this early, before
# authenticating the patron, so that we have the username on authentication failures.
if auth.type.lower() == "basic" and auth.username:
flask.request.environ["REMOTE_USER"] = auth.username

patron = self.authenticated_patron(auth)
if isinstance(patron, Patron):
setattr(flask.request, "patron", patron)

# Set REMOTE_USER again here for non-basic auth flows, using the patron's
# authorization identifier to provide detail in access logs.
if (
flask.request.environ.get("REMOTE_USER") is None
and patron.authorization_identifier
):
flask.request.environ["REMOTE_USER"] = patron.authorization_identifier

return patron

def authenticated_patron(
Expand Down
42 changes: 42 additions & 0 deletions tests/manager/api/controller/test_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,48 @@ def test_authenticated_patron_from_request(
assert result.status_code == 401
assert getattr(flask.request, "patron") is None

def test_authenticated_patron_from_request_sets_remote_user_basic_auth(
self, circulation_fixture: CirculationControllerFixture
):
"""REMOTE_USER is set from basic auth credentials, even on auth failure."""
# Successful basic auth sets REMOTE_USER to the username.
with circulation_fixture.request_context_with_library(
"/", headers=dict(Authorization=circulation_fixture.valid_auth)
):
result = circulation_fixture.controller.authenticated_patron_from_request()
assert isinstance(result, Patron)
assert flask.request.environ["REMOTE_USER"] == "unittestuser"

# Failed basic auth still sets REMOTE_USER to the submitted username.
with circulation_fixture.request_context_with_library(
"/",
headers=dict(Authorization=circulation_fixture.invalid_auth),
):
result = circulation_fixture.controller.authenticated_patron_from_request()
assert not isinstance(result, Patron)
assert flask.request.environ["REMOTE_USER"] == "user1"

def test_authenticated_patron_from_request_sets_remote_user_non_basic_auth(
self, circulation_fixture: CirculationControllerFixture
):
"""REMOTE_USER is set from patron.authorization_identifier for non-basic auth."""
patron = circulation_fixture.default_patron
patron.authorization_identifier = "patron-auth-id"

with (
patch.object(
circulation_fixture.manager.auth,
"authenticated_patron",
return_value=patron,
),
circulation_fixture.request_context_with_library(
"/", headers=dict(Authorization="Bearer some-token")
),
):
result = circulation_fixture.controller.authenticated_patron_from_request()
assert result == patron
assert flask.request.environ["REMOTE_USER"] == "patron-auth-id"

def test_authenticated_patron_invalid_credentials(
self, circulation_fixture: CirculationControllerFixture
):
Expand Down
Loading