Repository navigation
Conversation
The publication filter in experiment search referenced ScoreSet without tying it to the experiment, so SQLAlchemy cross-joined scoresets: every experiment matched as soon as any score set cited the publication, private ones included. Match through the experiment's own score sets, counting only those the caller may see: published ones, plus the caller's own in /me search. The visibility predicate ignores admin and mapper roles, which search has never widened results for. The meta-analysis filter also reads private score sets but is left as is. It exposes only whether an experiment is a meta-analysis experiment, which is uniform across its score sets and already disclosed by the create route's 409 to anyone who may add to it.
The add and remove member routes looked up the collection and member and could answer 404 or 409 before any permission check. A caller with no access could tell a missing collection from a private one by the 404 detail, and tell whether a private record was linked to a collection by 409 against 403 or 404. Check READ on the collection, then on the member, reporting either as missing, then the edit permission, and only then the association. An editor can no longer remove a member they cannot read; the collection view already hides those members from them. A non-editor removing an unlinked member now gets 403 rather than 409.
Creating a score set in another user's private meta-analysis experiment returned the meta-analysis 409, and any other private experiment a 404 worded differently from a missing one, so both revealed that the experiment exists. Report an unreadable experiment exactly like a missing one, with the URN wording an existing test already expected, and check ADD_SCORE_SET before the 409. The supersede 409 named the superseding score set, which can be another contributor's private draft. Name it only to a caller who may read it.
The two conditions were joined with Python's `and`, which evaluates the first SQLAlchemy clause for truthiness and returns it, so only the identifier filter reached SQL and a search under one database returned matching identifiers from every database.
Coverage Report for CI Build 40747011804Coverage increased (+0.04%) to 90.441%Details
Uncovered ChangesNo uncovered changes found. Coverage Regressions12 previously-covered lines in 3 files lost coverage.
Coverage Stats
💛 - Coveralls |
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.
Summary
Fixes several places where the Python permission layer let callers learn about private records through search results, status codes or error details. The RLS inventory in #832 turned these up. RLS (#808) keeps the Python checks as a second line of defense, so they need fixing either way.
publicationIdentifiersfilter used an uncorrelatedScoreSet.publication_identifiers.any(...), which cross-joinedscoresets. As a result, every experiment matched whenever any score set, private ones included, cited the publication. The filter now matches through the experiment's own score sets, counting only those the caller can see: published ones, plus the caller's own in/me/experiments/search.ADD_SCORE_SET, are now checked before the meta-analysis 409, so another user's private experiment gives the same 404 as a missing one. The supersede 409 names the superseding score set only when the caller can read it, because it may be another contributor's private draft./publication-identifiers/search/{db_name}/{identifier}used a Pythonandbetween two clauses, which silently dropped thedb_namefilter.The #832 inventory also lists the identifier list and autocomplete routes, which return identifiers linked only to private records. Those are left to the identifier-table RLS policies that #949 and #960 prepare for, not filtered here.
Behavior changes a reviewer may notice:
POST /score-sets/now returnsexperiment with URN '<urn>' not found, matching the private case, in place of "The requested experiment does not exist".Part of #832
Tests
Added regression tests in
tests/routers/for each fix. Each one fails on the old code and passes on this branch:test_experiments.py: publication filter, public search and/mesearch.test_collections.py: collection and member probes, add and remove, score sets and experiments.test_score_set.py: private meta-analysis experiment; supersede conflict.test_publication_identifiers.py:db_namefilter.Ran
bin/checkand the PR CI set:bin/t tests/ -n 6 -m "not network and not slow"gave 5450 passed, 22 skipped.Checklist
release-<year>.<n>.0branch otherwise; never
mainmavedb-uiPR regeneratessrc/schema/openapi.d.ts: no schema change