fix(solicitations): require login on marketplace views (closes #198)#202
fix(solicitations): require login on marketplace views (closes #198)#202jjackson wants to merge 1 commit into
Conversation
…non fallback) Fixes #198. The previous _get_public_data_access fell back to a CLI token in ~/.commcare-connect/token.json when there was no OAuth session, but that file is never present on the AWS ECS Fargate container — there is no deployment mechanism that puts one there. Production's LabsRecordDataView requires IsAuthenticated + TokenHasScope(['export']) for every request, including queries that filter to public=True, so the anonymous code path could never have worked in practice. Net effect was a silent "No solicitations available" listing + 404s on detail URLs whenever an unauthenticated visitor hit /solicitations/. Drop the broken fallback and make the requirement explicit by adding LabsLoginRequiredMixin to PublicSolicitationListView and PublicSolicitationDetailView. Anonymous viewers are redirected to /labs/login/?next=… so they land back on the marketplace after auth. The "Public*" class and URL names are kept to avoid downstream churn in mcp_tools, ace plugin, and links in emails/docs — only the visibility model changes. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
🤖 AI Code ReviewCode ReviewSummaryThis PR removes a broken CLI token fallback mechanism and makes marketplace views explicitly require authentication. The changes are well-documented and the tests correctly verify the new behavior. Findings🔵 Suggestion - Test robustness: Handle malformed query stringsFile: next_param = dict(p.split("=", 1) for p in parts.query.split("&"))["next"]This will raise a from urllib.parse import parse_qs
next_param = parse_qs(parts.query)["next"][0]Or add error handling to make the test intent clearer if malformed URLs are encountered. 🔵 Suggestion - Test coverage: Verify authenticated access still worksFile: The tests verify that anonymous users are redirected, but there's no test confirming that authenticated users can still access these views. Consider adding a test like: @pytest.mark.django_db
def test_public_list_works_for_authenticated_user(authenticated_client):
url = reverse("solicitations:public_list")
resp = authenticated_client.get(url)
assert resp.status_code == 200This ensures the happy path still works after the refactoring. 🔵 Suggestion - Error handling considerationFile: The
Verdict✅ Code looks good overall. The refactoring correctly removes dead code and makes authentication requirements explicit. The tests properly verify the new redirect behavior. The suggestions above would improve robustness but aren't blockers. Powered by Claude — auto-generated review |
Product Description
The
/solicitations/marketplace listing and/solicitations/<id>/detail pages now require a labs login. Anonymous visitors are redirected to/labs/login/?next=…and land back on the page they wanted after they auth.Before this change, anonymous visitors saw a misleading empty "No solicitations available" listing and 404s on detail pages, even though the records existed and were marked
is_public=true. Logged-in users were unaffected.Technical Summary
Closes #198.
The previous
_get_public_data_accesshelper incommcare_connect/solicitations/views.pytried to fall back to a CLI OAuth token loaded from~/.commcare-connect/token.jsonwhenever the request had no session. That file is a dev-time convenience — nothing inDockerfile,docker/start, the ECS task definition, or the deploy workflow puts one on the production container. The fallback therefore raised `ValueError` from `SolicitationsDataAccess(access_token=None)`, which got swallowed by the views' broad `except Exception` and rendered an empty listing (`PublicSolicitationListView`) or a 404 (`PublicSolicitationDetailView`) — silently, with the failure visible only in server logs.The deeper reason the fallback couldn't work even in principle: production's `LabsRecordDataView` (`dimagi/commcare-connect`) declares `permission_classes = [IsAuthenticated, TokenHasScope]` with `required_scopes = ["export"]` on every request. The `public=True` filter on `LabsRecord` is an ACL on which records get returned, not an auth-bypass on the endpoint. Any caller — including `labs` rendering an anonymous marketplace — needs a real OAuth bearer token.
So instead of building a service-account token path in labs (which would require designating a user, configuring an env var, and re-using the existing `get_valid_access_token` / refresh-token plumbing), this PR honestly admits that the marketplace requires a login. The two views gain `LabsLoginRequiredMixin` and use the regular `_get_data_access` helper. The dead `_get_public_data_access` function is removed.
Class names (`PublicSolicitationListView`, `PublicSolicitationDetailView`) and URL names (`public_list`, `public_detail`) are left as-is to avoid downstream churn in `mcp_tools.py`, the ACE plugin (which links to these URLs in solicitation invite emails), and any human-authored references. Only the visibility model changes; the surface keeps its name.
Safety Assurance
Safety story
The change is strictly narrowing: every previously-anonymous request now redirects to `/labs/login/` instead of being served. There is no behavior change for authenticated users — they go through the same `_get_data_access(request)` codepath the manage views use, and `get_public_solicitations()` returns the same `public=True` records as before.
Two ways this could go wrong:
Automated test coverage
QA Plan
On labs, post-deploy:
Labels & Review
🤖 Generated with Claude Code