Skip to content

Normalize the activation fingerprint so www/http variants of one site match - #28

Merged
mzakash merged 1 commit into
surecart:release/v1.2.3from
vanshk141999:fix/normalize-activation-fingerprint
Oct 21, 2026
Merged

mzakash merged 1 commit into
surecart:release/v1.2.3from
vanshk141999:fix/normalize-activation-fingerprint

Conversation

@vanshk141999

Copy link
Copy Markdown
Contributor

Problem

Activation::create() and update() send esc_url_raw( get_site_url() ) as the activation fingerprint, without normalising it. The API matches fingerprints exactly ("If an activation already exists with the same fingerprint then it will be updated"), so URL variants of one site become separate activations:

  • https://www.example.com vs https://example.com
  • http:// vs https://. get_site_url() picks the scheme from the current request via is_ssl(), so one site can send both.
  • Host case, and a trailing slash.

A customer reported this on a product built on this SDK: "Sometimes I am using SureForms on www.CASP.ca, and sometimes on CASP.ca. Your system somehow decided that these are two separate domains."

Change

New Activation::get_fingerprint(), used by both create() and update(). It:

  • forces the https scheme;
  • lowercases the host and drops one leading www. label (wwwexample.com and shop.www.example.com are left alone);
  • keeps the port and path, since a subdirectory install or a different port can be a separate site, and removes a trailing slash;
  • falls back to the previous raw value if the URL has no parsable host;
  • is filterable via surecart_licensing_fingerprint( $fingerprint, $site_url ), following the existing surecart_licensing_* filter naming.

Regression analysis

  • Most sites send exactly the same value as before. For a canonical https, non-www, lowercase URL the fingerprint is byte-identical to today's, so nothing changes for that most common setup. Only sites that were already producing variants get a new value. This is pinned by test_get_fingerprint_is_unchanged_for_a_canonical_https_url.
  • Activated sites aren't touched. Nothing in the SDK resends a fingerprint for an existing activation. create() only runs from License::activate(), and update() has no callers. The new value is only sent the next time a license is activated.
  • Deactivate, then reactivate: deactivate() deletes the old activation by ID, so the new fingerprint creates a single activation. No duplicate.
  • ⚠ Remaining risk: a www/http site whose stored activation ID was lost (database restore, migration, reinstall) and that activates again without deactivating first. Today its identical raw fingerprint updates the existing activation. After this change, the old raw-fingerprint activation stays on the server and a new one is created, using a second seat once. It could be removed with an API-side match on the legacy value, or by matching normalised fingerprints server-side. Sites can also restore the old behaviour with add_filter( 'surecart_licensing_fingerprint', function ( $fp, $url ) { return esc_url_raw( $url ); }, 10, 2 );.
  • PHP compatibility: no syntax beyond what src/ already uses. wp_parse_url() has been in WordPress since 4.4. php -l passes on 7.4 and 8.0.

Tests

New tests/unit/ActivationFingerprintTest.php (7 tests):

  • canonical URL unchanged;
  • www, scheme, case and trailing-slash variants all map to one value;
  • path and port kept;
  • only a leading www. label is stripped;
  • raw fallback when the URL has no host;
  • filter override;
  • create() sends the normalised value.

Results (WordPress test suite, local MySQL):

  • ActivationFingerprintTest: OK on PHP 7.4.30 (7 tests, 14 assertions) and on PHP 8.4.
  • LicenseActivationTest, the existing suite: unchanged, OK (6 tests, 18 assertions) on 7.4.30 and 8.4.
  • Revert check: with src/Activation.php reverted, test_create_sends_the_normalized_fingerprint fails ("Failed asserting that two strings are identical"). Before the change all 7 new tests errored on the missing method.
  • phpcs (WPCS, run with PHP 7.4 like CI): clean on src/ and the new test.

Notes (out of scope, not changed)

  • update() is dead code that would crash if called. It calls $this->client->license()->get_id(), which doesn't exist in License. Nothing calls update(), so I didn't touch it or test it beyond the shared helper.
  • tests/unit/SettingsTest.php crashes before running on current PHPUnit polyfills (setUp() has no : void return type). CI only runs PHPCS, so this isn't caught.

Questions for maintainers

  1. Would you rather normalise URL-type fingerprints on the API side as well, so sites on older SDK versions also benefit, or do it only here?
  2. Is an API-side match on the legacy raw fingerprint feasible, to close the remaining risk described above?

get_site_url() was sent as-is, so https://www.example.com, https://example.com
and http://example.com created separate activations for the same site. The
scheme also follows the current request via is_ssl(), so one site could send
both http and https.

Activation::get_fingerprint() now forces https, lowercases the host, drops a
leading www. label and a trailing slash, and keeps the port and path. A
canonical https non-www URL keeps its exact previous fingerprint. Exposed via
the surecart_licensing_fingerprint filter.
@vanshk141999
vanshk141999 marked this pull request as ready for review October 20, 2026 09:03
@mzakash
mzakash changed the base branch from main to release/v1.2.3 October 21, 2026 07:42
@mzakash
mzakash merged commit 33ba74a into surecart:release/v1.2.3 Oct 21, 2026
2 checks passed
@mzakash mzakash mentioned this pull request Oct 21, 2026
3 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants