girder-collection-review
Share a Girder collection with anonymous peer reviewers via a single revocable access key.
Reviewers never create Girder accounts, cannot change anything, and lose access the moment the collection owner ends the review.
Workflow
- The collection owner (ADMIN on the collection) opens Actions → Manage review on the collection page and opens a review round. The server provisions a throwaway read-only account plus an API key, and shows the key once.
- The owner passes the key to the journal editor, who distributes it to reviewers.
- A reviewer visits
#review, pastes the key, and gets the collection hierarchy on a page with no navigation chrome — read only, with downloads working. - The owner ends the review. The key stops working immediately, live reviewer sessions die, every ACL entry for the reviewer is stripped, and the throwaway account is deleted.
Install
pip install -e .
cd girder_collection_review/web_client && npm ci && npm run build
The web client bundle must be built before starting Girder:
registerPluginStaticContent hashes the files in web_client/dist/ at plugin load time, so
a missing dist/ fails server startup.
Develop
tox -e lint # ruff check
tox -e pytest # pytest with coverage, 4-way xdist
tox -e format # ruff format + ruff check --fix
Tests live in girder_collection_review/tests/ and use the pytest-girder fixtures
(db, server, admin, user, fsAssetstore); plugin-specific fixtures are in
tests/conftest.py. Every test module needs
pytestmark = pytest.mark.plugin('collection_review') — pytest only honors module- and
class-level pytestmark, so it cannot be hoisted into the conftest.
Browser end-to-end checks live in girder_collection_review/tests/e2e/ and are run by hand
with ./run.sh — they need a built web client, a running Girder, a droppable Mongo and a
Chrome, so they are not part of tox -e pytest. See that directory's README.md.
JS, pug and stylus linting is self-contained — it does not need a girder checkout:
npm ci
npm run lint # eslint + pug-lint + stylelint
npm run format # eslint --fix
The rules are the same ones girder core applies to plugin web clients (@girder/eslint-config
and @girder/pug-lint-config, plus stylelint-stylus/standard), reproduced in this repo's
root package.json so they run standalone and in CI.
Trust model
Several choices here are deliberate and non-obvious. Changing them will quietly weaken the
plugin, so the reasoning is recorded next to each one in the source, and enumerated in
CLAUDE.md.
The reviewer account cannot be logged into. It is created passwordless (salt = None),
which User().authenticate() refuses outright, and its email is on the RFC 2606 reserved
.invalid domain. The domain matters: PUT /user/password/temporary is a public route that
works on passwordless accounts, so a routable reviewer mailbox would let whoever controls it
upgrade the account to a full USER_AUTH session. status is also set to disabled, but
only as an operator-visible marker — neither getCurrentUser nor ApiKey().createToken
consults verifyLogin.
Read-only is enforced by a request guard, not by token scope. The review key is scoped
[core.data.read, core.user_info.read], but scope alone is not sufficient:
PUT /collection/:id is declared @access.user(scope=TokenScope.DATA_READ) in core, and
plugins declare worse. A review token is therefore more capable than an anonymous visitor.
lib/guard.py binds auth.user.get and rejects any non-safe HTTP method from a review
session, which closes the whole class of mis-scoped routes at once.
Ending a review revokes the key, not just the review document. POST /api_key/token is
public and never sees the review record, so a reviewer who kept the key could keep minting
tokens. Closing a review removes the API key, which makes Token().clearForApiKey drop
every derived token. The key's tokenDuration is also set from the review's duration, so it
caps token lifetime independently.
An auth cookie is set. Download links in the hierarchy widget are plain <a href>
navigations that carry no Girder-Token header. Every cookie=True route in core is
read-only, and the guard rejects non-safe methods for the session, so the cookie adds no
write surface. Note that the cookie is path=/ and replaces any existing girderToken in
that browser; the reviewer page warns before starting a session if somebody is already
signed in.
Known limitations
- The key is not truly single-view.
ApiKeyexposeskeyatREADlevel, so a site admin can recover it fromGET /api_key?userId=.... The UI shows it once as a convention, not a guarantee. - The site-wide
core.api_keysswitch is bypassed. Only the core REST layer checks it; the model methods this plugin uses do not. - Reviewer search returns nothing.
GET /resource/searchdeclares no scope, so a review token is treated as anonymous inside that handler. - A deleted collection can orphan a review.
DELETE /collection/:iddispatches the removal to a Celery worker where this plugin is not loaded, so no event fires. Read paths auto-close such reviews, and a site admin can list and close them withGET /reviewandDELETE /review/:id. - No throttle on key submission. Keys are 40 characters from a CSPRNG, and the core
POST /api_key/tokenroute is unthrottled anyway, so a throttle here would buy nothing. The real risk is key leakage in transit; use a short duration and one key per reviewer where practical. - No maximum review duration.
durationis only validated as positive.
Settings
| Key | Meaning |
|---|---|
collection_review.default_duration |
Days a review key stays valid when the owner does not specify a duration. Default 90. |
Endpoints
| Route | Purpose |
|---|---|
POST /review |
Open a review round. Returns the review plus its key. |
GET /review |
List reviews for a collection (ADMIN), or all of them (site admin, for cleaning up orphans). |
GET /review/:id |
Read one review. |
DELETE /review/:id |
Close a review and revoke access. Idempotent. |
POST /review/session |
Reviewer-facing: exchange a key for a read-only session. |
GET /review/session |
Reviewer-facing: current session, for reload and end-of-review detection. |
DELETE /review/session |
Reviewer-facing: end the session. |
License
BSD 3-Clause. See LICENSE.
Metadata
Release files for girder-collection-review 1.0.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| girder_collection_review-1.0.0.tar.gz | 71.9 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| girder_collection_review-1.0.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 97.8 kB
Release files / girder_collection_review-1.0.0.tar.gz
| Download URL | girder_collection_review-1.0.0.tar.gz |
|---|---|
| Size | 71.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
586f8a3ea853df719c135718e7f682ac00f0cb07c88f7cbc881b37faef48689f
|
|
BLAKE2b-256 checksum How to use checksums |
fe63139cb47398ad75009232f5097b01bc2fe2f1e8b70ffbf8cfdab186071b5c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 25, 2026.
Transparency logRelease files / girder_collection_review-1.0.0-py3-none-any.whl
| Download URL | girder_collection_review-1.0.0-py3-none-any.whl |
|---|---|
| Size | 25.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
20ebebaa2259ed34a2ced81fa39d447f9c4f69bc31349789f26eed2d55e317b7
|
|
BLAKE2b-256 checksum How to use checksums |
293a7c2d4935d46cb9d368d794e7716562333f44699ea41f93bf6d19c3312a05
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 25, 2026.
Transparency log