diff --git a/CHANGELOG.md b/CHANGELOG.md index 69a21dc..3767f87 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ ## [Unreleased] +## [3.2.1] - 2026-06-17 + +- Fix QGIS plugin repository security scan issues blocking release: + add an explicit timeout to the OAuth callback unblock request, and + replace the high-entropy multipart form boundary with a readable + constant + ## [3.2.0] - 2026-06-17 - Add support for QGIS 4.x (Qt6-based) releases, while remaining diff --git a/RELEASING.md b/RELEASING.md index 823675b..12b3fed 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -11,21 +11,27 @@ published, the [`release.yml`](.github/workflows/release.yml) workflow runs `felt/metadata.txt` `version=` field — overriding whatever value is committed there. -- Tags use **no `v` prefix** (e.g. `3.2.0`, not `v3.2.0`). The tag name becomes +- Tags use **no `v` prefix** (e.g. `3.2.1`, not `v3.2.1`). The tag name becomes the version string verbatim, so a `v` prefix would ship a version literally - named `v3.2.0`. -- The `version=` field in `felt/metadata.txt` and the entries in - `CHANGELOG.md` are cosmetic — they do not drive the release and are not - prominently surfaced to users. Keep them consistent for hygiene, but the tag - is what matters. + named `v3.2.1`. +- The `version=` field in `felt/metadata.txt` is **cosmetic** — `qgis-plugin-ci` + overrides it with the tag name at release time. Keep it consistent with the + tag for hygiene, but the tag is what determines the shipped version. +- `CHANGELOG.md` is **not** cosmetic. At release time `qgis-plugin-ci` reads it + and injects the matching version's notes into the packaged `metadata.txt` + `changelog=` field, which QGIS surfaces to users in the Plugin Manager. So the + `changelog=` field in `metadata.txt` is intentionally left empty and must not + be hand-edited — maintain release notes in `CHANGELOG.md` only. ## Steps -1. **(Optional) Update the changelog and metadata on a branch.** Move items out - of `[Unreleased]` in `CHANGELOG.md` into a new `## [] - ` - section, and bump `version=` in `felt/metadata.txt` to match. Open a PR and - merge to `main`. This is cosmetic hygiene, not required for the release to - succeed. +1. **Update the changelog and metadata on a branch.** Move items out of + `[Unreleased]` in `CHANGELOG.md` into a new `## [] - ` section, + and bump `version=` in `felt/metadata.txt` to match. Open a PR and merge to + `main`. The release will still build without this, but the `CHANGELOG.md` + entry is what populates the user-visible changelog in the published package + (see Versioning above), so do it before tagging. The `version=` bump itself is + cosmetic hygiene since the tag overrides it. 2. **Create a GitHub Release** with a new tag (e.g. `3.2.0`), targeting `main`. Publishing the release triggers [`release.yml`](.github/workflows/release.yml), @@ -47,3 +53,10 @@ there. builds an `-alpha` package via `qgis-plugin-ci package` and uploads it as a CI artifact (with a download link posted on the PR). This is for testing pre-release builds and is not part of the release path. +- **If plugins.qgis.org rejects the upload** (e.g. its automated security scan + blocks the package), do **not** reuse the same version number to resubmit. + The GitHub tag/release for that version is already cut against the old code + and should be treated as immutable. Fix the issues on a branch, bump to a new + patch version (e.g. `3.2.0` → `3.2.1`) with a matching `CHANGELOG.md` entry, + cut a new tag/release, and upload that. Leave the blocked release in place as + a record of the attempt. diff --git a/felt/core/api_client.py b/felt/core/api_client.py index 8375488..01523ec 100644 --- a/felt/core/api_client.py +++ b/felt/core/api_client.py @@ -72,6 +72,9 @@ class FeltApiClient: UPDATE_LAYER_ENDPOINT = '/maps/{}/layers' LAYER_GROUPS_ENDPOINT = '/maps/{}/layer_groups' + # boundary marker used when building multipart/form-data upload bodies + MULTIPART_BOUNDARY = 'QGISFeltPluginFormBoundary' + def __init__(self): # default headers to add to all requests self.headers = { @@ -338,15 +341,19 @@ def create_upload_file_request(self, b'Host', parameters.url[len('https://'):-1].encode() ) + boundary = self.MULTIPART_BOUNDARY network_request.setRawHeader( b"Content-Type", - b"multipart/form-data; boundary=QGISFormBoundary2XCkqVRLJ5XMxfw5") + "multipart/form-data; boundary={}".format(boundary).encode()) + + delimiter = "--{}\r\n".format(boundary).encode() + closing_delimiter = "--{}--\r\n".format(boundary).encode() # build the form content as bytes: PyQt6 does not permit appending # strings to QByteArray form_content = b'' for name, value in parameters.to_form_fields().items(): - form_content += b"--QGISFormBoundary2XCkqVRLJ5XMxfw5\r\n" + form_content += delimiter form_content += b"Content-Disposition: form-data; " form_content += f"name=\"{name}\"".encode() form_content += b"\r\n" @@ -354,7 +361,7 @@ def create_upload_file_request(self, form_content += str(value).encode() form_content += b"\r\n" - form_content += b"--QGISFormBoundary2XCkqVRLJ5XMxfw5\r\n" + form_content += delimiter form_content += b"Content-Disposition: " form_content += \ f"form-data; name=\"file\"; filename=\"{filename}\"\r\n".encode() @@ -364,7 +371,7 @@ def create_upload_file_request(self, form_content += content form_content += b"\r\n" - form_content += b"--QGISFormBoundary2XCkqVRLJ5XMxfw5--\r\n" + form_content += closing_delimiter form_data = QByteArray(form_content) content_length = form_data.length() diff --git a/felt/core/auth.py b/felt/core/auth.py index aa368f6..4b1f0af 100644 --- a/felt/core/auth.py +++ b/felt/core/auth.py @@ -181,9 +181,10 @@ def force_stop(): """ # we have to dummy a dummy request in order to abort the # blocking handle_request() loop - # pylint: disable=missing-timeout - requests.get("http://127.0.0.1:{}".format(REDIRECT_PORT)) - # pylint: enable=missing-timeout + requests.get( + "http://127.0.0.1:{}".format(REDIRECT_PORT), + timeout=10 + ) def close_server(self): """ diff --git a/felt/metadata.txt b/felt/metadata.txt index c6be73e..0a75dec 100644 --- a/felt/metadata.txt +++ b/felt/metadata.txt @@ -12,7 +12,7 @@ name=Add to Felt qgisMinimumVersion=3.22 qgisMaximumVersion=4.99 description=Create a collaborative Felt (felt.com) map from QGIS -version=3.2.0 +version=3.2.1 author=Felt email=support@felt.com @@ -24,7 +24,9 @@ repository=https://github.com/felt/qgis-plugin # Recommended items: -# Uncomment the following line and add your changelog: +# Leave this empty. The changelog is maintained in CHANGELOG.md and +# injected here automatically by qgis-plugin-ci at release time +# (see .github/workflows/release.yml). Do not edit this field by hand. changelog= # Tags are comma separated with spaces allowed diff --git a/felt/test/test_api_client.py b/felt/test/test_api_client.py index 398b9b1..34751a9 100644 --- a/felt/test/test_api_client.py +++ b/felt/test/test_api_client.py @@ -277,7 +277,7 @@ def test_create_upload_file_request(self): b'form-data; name="file"; filename="test.gpkg"', body) self.assertIn(b'GPKG\x00\x01binary', body) self.assertTrue( - body.endswith(b'--QGISFormBoundary2XCkqVRLJ5XMxfw5--\r\n')) + body.endswith(b'--QGISFeltPluginFormBoundary--\r\n')) self.assertEqual(request.rawHeader(b'Content-Length'), str(len(body)).encode())