Unguessable slugs; accept slug or PK on detail API#623
Open
skaphan wants to merge 1 commit intoartoonie:mainfrom
Open
Unguessable slugs; accept slug or PK on detail API#623skaphan wants to merge 1 commit intoartoonie:mainfrom
skaphan wants to merge 1 commit intoartoonie:mainfrom
Conversation
The slug generator previously appended a counter (-1, -2, ...) to make slugs unique across records that shared a title. Two problems with that: 1. Slugs are enumerable. Anyone who can guess a title can walk the counter space and browse other visualizations the system has created, even those not listed publicly. 2. Counter values are reused across database resets. On an ephemeral or externally-managed instance, a stale client PATCHing by PK can silently overwrite an unrelated record that happened to land on the same integer after a reset. _get_unique_slug now appends 12 random hex chars (secrets.token_hex(6)) instead of an incrementing counter. The title prefix is preserved for SEO and admin browsing; the random suffix makes slugs non-enumerable and unique across resets. The existing uniqueness loop is retained as a belt-and-suspenders guard for the astronomically-rare 48-bit collision. No schema change, no data migration: old records keep their counter-style slugs and remain valid. To let clients address records by the new unguessable slug instead of the reusable PK, the three write-capable ModelViewSets now use a new PkOrSlugLookupMixin. Its get_object() treats digit-only lookup values as PKs (backward compat for existing clients), falling back to slug lookup if the PK is not found; non-digit values dispatch to slug. No URL-routing changes. Tests updated: slug assertions regex-match the new format. Added a new test for dual PK/slug lookup on the detail endpoint. Co-Authored-By: Claude Opus 4.7
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
_get_unique_slug()now appends 12 random hex chars (secrets.token_hex(6)) instead of an incrementing-Ncounter. Slugs stay title-prefixed (good for SEO and admin browsing) but become non-enumerable.JsonOnlyViewSet,VerboseViewSet, andBallotpediaViewSetaccept either the integer PK or the slug as the detail URL's lookup value, via a smallPkOrSlugLookupMixin. No URL routing changes.Motivation
/v/<title>,/v/<title>-1,/v/<title>-2, ...) to browse other visualizations the instance has stored. Random suffixes close this off.Backward compatibility
PkOrSlugLookupMixin.get_object()dispatches digit-only lookup values as PKs (falling back to slug if not found), and non-digit values as slugs. Clients can opt into slug-based addressing at their own pace.Tests
-Nformat to assert the new-<12-hex>format (regex).test_detail_accepts_pk_and_slugexercises the dual-lookup mixin: both/api/visualizations/<pk>/and/api/visualizations/<slug>/resolve to the same record.Test plan
./scripts/run-tests.shpassescurl /api/visualizations/<pk>/andcurl /api/visualizations/<slug>/both return the same record