Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1236b861b8 | ||
|
|
cd78800a65 |
@@ -104,3 +104,21 @@ From the core checkout, labels can be synced once a local `GITEA_TOKEN` is avail
|
|||||||
cd /mnt/DATA/git/govoplan-core
|
cd /mnt/DATA/git/govoplan-core
|
||||||
/mnt/DATA/git/govoplan/tools/gitea/gitea-sync-labels.py --root /mnt/DATA/git/govoplan-forms-runtime --apply
|
/mnt/DATA/git/govoplan/tools/gitea/gitea-sync-labels.py --root /mnt/DATA/git/govoplan-forms-runtime --apply
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Git-source WebUI package
|
||||||
|
|
||||||
|
The repository root exposes `@govoplan/forms-runtime-webui` for Git-tagged release
|
||||||
|
dependencies. It mirrors the owning `webui/package.json` version, public
|
||||||
|
TypeScript/CSS exports and peer requirements, with entry paths under
|
||||||
|
`webui/src`. Consumers provide the shared Core/React peers; the facade runs no
|
||||||
|
development or install scripts. The source archive contains `webui/src`, this
|
||||||
|
README and any repository license file. Run module development checks from `webui/`; Python
|
||||||
|
installation remains governed by `pyproject.toml`.
|
||||||
|
|
||||||
|
Das Repository stellt `@govoplan/forms-runtime-webui` am Wurzelpfad für versionierte
|
||||||
|
Git-Abhängigkeiten bereit. Version, öffentliche TypeScript-/CSS-Exporte und
|
||||||
|
Peer-Anforderungen entsprechen `webui/package.json`; die Einstiegspfade liegen
|
||||||
|
unter `webui/src`. Gemeinsame Core-/React-Peers stellt die einbindende Anwendung
|
||||||
|
bereit. Die Fassade führt keine Entwicklungs- oder Installationsskripte aus.
|
||||||
|
Entwicklungsprüfungen bleiben in `webui/`, die Python-Installation weiterhin in
|
||||||
|
`pyproject.toml` definiert.
|
||||||
|
|||||||
+29
-3
@@ -1,8 +1,34 @@
|
|||||||
{
|
{
|
||||||
"name": "@govoplan/forms-runtime",
|
"name": "@govoplan/forms-runtime-webui",
|
||||||
"version": "0.1.20",
|
"version": "0.1.22",
|
||||||
"private": true,
|
"private": true,
|
||||||
"description": "Definition-aware form submissions and service launch for GovOPlaN.",
|
"description": "Definition-aware form submissions and service launch for GovOPlaN.",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"peerDependencies": {}
|
"peerDependencies": {
|
||||||
|
"@govoplan/core-webui": "^0.1.18",
|
||||||
|
"lucide-react": "^1.23.0",
|
||||||
|
"react": ">=19.2.7 <20",
|
||||||
|
"react-dom": ">=19.2.7 <20",
|
||||||
|
"react-router": ">=8.3.0 <9"
|
||||||
|
},
|
||||||
|
"main": "webui/src/index.ts",
|
||||||
|
"module": "webui/src/index.ts",
|
||||||
|
"types": "webui/src/index.ts",
|
||||||
|
"exports": {
|
||||||
|
".": {
|
||||||
|
"types": "./webui/src/index.ts",
|
||||||
|
"import": "./webui/src/index.ts"
|
||||||
|
},
|
||||||
|
"./styles/forms-runtime.css": "./webui/src/styles/forms-runtime.css"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@govoplan/core-webui": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"webui/src",
|
||||||
|
"README.md",
|
||||||
|
"LICENSE"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "govoplan-forms-runtime"
|
name = "govoplan-forms-runtime"
|
||||||
version = "0.1.20"
|
version = "0.1.22"
|
||||||
description = "Definition-aware form submissions and service launch for GovOPlaN."
|
description = "Definition-aware form submissions and service launch for GovOPlaN."
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
requires-python = ">=3.12"
|
requires-python = ">=3.12"
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ from govoplan_core.core.dsar import (
|
|||||||
DsarSubjectRef,
|
DsarSubjectRef,
|
||||||
dsar_capability_name,
|
dsar_capability_name,
|
||||||
)
|
)
|
||||||
|
from govoplan_forms_runtime.backend.email_normalization import normalize_status_email as _normalized_email
|
||||||
from govoplan_forms_runtime.backend.db.models import (
|
from govoplan_forms_runtime.backend.db.models import (
|
||||||
FormAcknowledgement,
|
FormAcknowledgement,
|
||||||
FormAssistedConfirmation,
|
FormAssistedConfirmation,
|
||||||
@@ -1116,21 +1117,6 @@ def _session(value: object) -> Session:
|
|||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
||||||
def _normalized_email(value: object) -> str | None:
|
|
||||||
candidate = str(value or "").strip().casefold()
|
|
||||||
if (
|
|
||||||
not candidate
|
|
||||||
or len(candidate) > 320
|
|
||||||
or candidate.count("@") != 1
|
|
||||||
or any(character.isspace() for character in candidate)
|
|
||||||
):
|
|
||||||
return None
|
|
||||||
local, domain = candidate.rsplit("@", 1)
|
|
||||||
if not local or "." not in domain or domain.startswith(".") or domain.endswith("."):
|
|
||||||
return None
|
|
||||||
return candidate
|
|
||||||
|
|
||||||
|
|
||||||
def _normalized_id(value: object) -> str | None:
|
def _normalized_id(value: object) -> str | None:
|
||||||
if value is None:
|
if value is None:
|
||||||
return None
|
return None
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
"""Existing Forms Runtime email-selector policy, shared by status access and DSAR.
|
||||||
|
|
||||||
|
This is selector canonicalization, not a transformation of submitted form values
|
||||||
|
or a general-purpose email-address validator.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def normalize_status_email(value: object) -> str | None:
|
||||||
|
candidate = str(value or "").strip().casefold()
|
||||||
|
if (
|
||||||
|
not candidate
|
||||||
|
or len(candidate) > 320
|
||||||
|
or candidate.count("@") != 1
|
||||||
|
or any(character.isspace() for character in candidate)
|
||||||
|
):
|
||||||
|
return None
|
||||||
|
local, domain = candidate.rsplit("@", 1)
|
||||||
|
if not local or "." not in domain or domain.startswith(".") or domain.endswith("."):
|
||||||
|
return None
|
||||||
|
return candidate
|
||||||
@@ -65,7 +65,7 @@ from govoplan_forms_runtime.backend.status_access import FormStatusAccessService
|
|||||||
|
|
||||||
MODULE_ID = "forms_runtime"
|
MODULE_ID = "forms_runtime"
|
||||||
MODULE_NAME = "Forms Runtime"
|
MODULE_NAME = "Forms Runtime"
|
||||||
MODULE_VERSION = "0.1.20"
|
MODULE_VERSION = "0.1.22"
|
||||||
PARTICIPATE_SCOPE = "forms_runtime:submission:participate"
|
PARTICIPATE_SCOPE = "forms_runtime:submission:participate"
|
||||||
ASSIST_SCOPE = "forms_runtime:submission:assist"
|
ASSIST_SCOPE = "forms_runtime:submission:assist"
|
||||||
READ_SCOPE = "forms_runtime:workspace:read"
|
READ_SCOPE = "forms_runtime:workspace:read"
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ from govoplan_forms_runtime.backend.db.models import (
|
|||||||
FormStatusAccessToken,
|
FormStatusAccessToken,
|
||||||
)
|
)
|
||||||
from govoplan_forms_runtime.backend.domain import FormInstance
|
from govoplan_forms_runtime.backend.domain import FormInstance
|
||||||
|
from govoplan_forms_runtime.backend.email_normalization import normalize_status_email as _normalize_email
|
||||||
|
|
||||||
|
|
||||||
STATUS_ACCESS_MODES = frozenset(
|
STATUS_ACCESS_MODES = frozenset(
|
||||||
@@ -616,21 +617,6 @@ def _consume_request_limit(grant: FormStatusAccessGrant, *, now: datetime) -> bo
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def _normalize_email(value: object) -> str | None:
|
|
||||||
candidate = str(value or "").strip().casefold()
|
|
||||||
if (
|
|
||||||
not candidate
|
|
||||||
or len(candidate) > 320
|
|
||||||
or candidate.count("@") != 1
|
|
||||||
or any(character.isspace() for character in candidate)
|
|
||||||
):
|
|
||||||
return None
|
|
||||||
local, domain = candidate.rsplit("@", 1)
|
|
||||||
if not local or "." not in domain or domain.startswith(".") or domain.endswith("."):
|
|
||||||
return None
|
|
||||||
return candidate
|
|
||||||
|
|
||||||
|
|
||||||
def _email_digest(grant_id: str, email: str) -> str:
|
def _email_digest(grant_id: str, email: str) -> str:
|
||||||
return hashlib.sha256(f"{grant_id}\0{email}".encode("utf-8")).hexdigest()
|
return hashlib.sha256(f"{grant_id}\0{email}".encode("utf-8")).hexdigest()
|
||||||
|
|
||||||
|
|||||||
Executable
+37
@@ -0,0 +1,37 @@
|
|||||||
|
import unittest
|
||||||
|
|
||||||
|
from govoplan_forms_runtime.backend.dsar_provider import _normalized_email
|
||||||
|
from govoplan_forms_runtime.backend.email_normalization import normalize_status_email
|
||||||
|
from govoplan_forms_runtime.backend.status_access import _normalize_email
|
||||||
|
|
||||||
|
|
||||||
|
class EmailNormalizationTests(unittest.TestCase):
|
||||||
|
def test_status_access_and_dsar_share_the_same_selector_policy(self) -> None:
|
||||||
|
self.assertIs(normalize_status_email, _normalize_email)
|
||||||
|
self.assertIs(normalize_status_email, _normalized_email)
|
||||||
|
|
||||||
|
def test_existing_selector_results_remain_exact_at_policy_boundaries(self) -> None:
|
||||||
|
boundary = "a" * 308 + "@example.org"
|
||||||
|
values = (
|
||||||
|
(None, None), (False, None), (0, None), ("", None), (" ", None),
|
||||||
|
(" Subject@Example.ORG ", "subject@example.org"),
|
||||||
|
("\u00a0Straße@BÜRO.Example\u00a0", "strasse@büro.example"),
|
||||||
|
("a+b@sub.example.org", "a+b@sub.example.org"),
|
||||||
|
("a..b@example..org", "a..b@example..org"), # Existing policy, not a new RFC validator.
|
||||||
|
("a b@example.org", None), ("a@exa\tmple.org", None), ("a@exa\u00a0mple.org", None),
|
||||||
|
("a@@example.org", None), ("@example.org", None), ("a@example", None),
|
||||||
|
("a@.example.org", None), ("a@example.org.", None),
|
||||||
|
(boundary, boundary), ("a" + boundary, None),
|
||||||
|
)
|
||||||
|
for value, expected in values:
|
||||||
|
with self.subTest(value=value):
|
||||||
|
self.assertEqual(expected, normalize_status_email(value))
|
||||||
|
|
||||||
|
def test_selector_canonicalization_does_not_mutate_submitted_values(self) -> None:
|
||||||
|
values = {"email": " Subject@Example.ORG ", "other": ["001", " ", None]}
|
||||||
|
self.assertEqual("subject@example.org", normalize_status_email(values["email"]))
|
||||||
|
self.assertEqual({"email": " Subject@Example.ORG ", "other": ["001", " ", None]}, values)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@govoplan/forms-runtime-webui",
|
"name": "@govoplan/forms-runtime-webui",
|
||||||
"version": "0.1.20",
|
"version": "0.1.22",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
|
|||||||
Reference in New Issue
Block a user