feat(forms): a per-form DigiD or eHerkenning sign-in level on both publish paths - #942
Merged
Merged
Conversation
… per form Every form a maker opened to the outside was anonymous: the form page path hard-coded minTrust 0 (not a value portaliq knows) and the registration form had no trust field. Both paths now carry a per-form sign-in level from portaliq's value set (low, substantial, high; absent is anonymous). A repeat form page save that names no level keeps a stored level instead of writing the anonymous entry back over it, and a form that asks for a sign-in gets no OpenRegister public create grant, since portaliq writes past RBAC itself. Parity rows form-digid-login and form-public updated. Fixes #935
The rule pushed RegistrationFormValidator over the phpmd class complexity threshold (52 of 50).
This was referenced Sep 27, 2026
Closed
Contributor
Quality Report — ConductionNL/buildiq @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| build | ✅ | ||||
| check-manifest | ✅ | ||||
| test-l10n | ✅ | ||||
| check-gitignore | ✅ | ||||
| check-nc-floor | ✅ | ||||
| format | ✅ | ||||
| check-l10n-js | ✅ | ||||
| check-schema-l10n | ✅ | ||||
| composer | ✅ | ✅ 106/106 | |||
| npm | ✅ | ✅ 752/752 | |||
| app:check-code | ⏭️ | ||||
| info.xml | ✅ | ||||
| REUSE | ❌ | ||||
| lockfile sync | ✅ | ||||
| PHPUnit | ✅ | ||||
| Newman | ✅ | ||||
| Playwright | ⏭️ deferred: E2E runs locally and on the promotion path only. This pull request targets development, so the suite is asked once per promotion into beta and main rather than once per push per open pull request. Run it on any branch from the Actions tab, or locally with npx playwright test. |
||||
| Hydra gates | ✅ |
Quality workflow — 2026-09-27 07:41 UTC
Download the full PDF report from the workflow artifacts.
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.
What the user saw
A maker could not say that a form may only be filled in after signing in with DigiD or eHerkenning. Every form opened to the outside was published as anonymous. The only way to put DigiD in front of a form was to make the whole portal DigiD-only.
Cause
externalFormProvisioningService.jshard-codedanonymous: truewithminTrust: 0on the create action and on the page.0is not a value portaliq knows (Opening a form page to external submitters writes a portal page portaliq cannot use #921).registrationFormschema had no trust field at all, so portaliq had nothing to enforce.The value set (coordinated with portaliq#725)
minTrustis one oflow,substantialorhigh, portaliq's own enum. There is noanonymousvalue: absence means anonymous. buildiq sends a level only when the maker chose one, and portaliq enforces it (portaliq#725, merged as portaliq#727, readsregistrationForm.minTrustinPortalFormBindingResolver::render()).What changed
minTrust(enum low|substantial|high, version 0.3.0 to 0.4.0).isPublicoff; ticking "Anyone may fill this in" removes the level; "No sign-in" removes the key rather than storing a value.0) and a form that is both public and signed-in, inRegistrationFormSignInLevel::assertValid(), called from the validator. It lives in its own class because the validator would otherwise cross the phpmd class complexity threshold.RegistrationFormPresenter::serve()) carriesminTrust.provisionPortalPage({ minTrust })handles the level three ways:minTruston the create action and on the collection, withoutanonymous;null(the maker explicitly chose no sign-in) makes both entries anonymous;minTrust: 0is never written, and a stored0is dropped on the next save (Opening a form page to external submitters writes a portal page portaliq cannot use #921 comment)._rbac: false(PortalObjectWriter), so the grant is not needed for the portal path; keeping it would let anyone POST straight to OpenRegister past the sign-in. The raw anonymous submit URL is not shown for such a form.form-digid-login: buildiqpartialtoyes, with new evidence and reachedOn, and a caveat (see below).form-public: the evidence and the defect note no longer citeminTrust: 0. The remaining Opening a form page to external submitters writes a portal page portaliq cannot use #921 items (actionid/label,pages: [], the hard-coded/portal) stay in its note.Tests (all red before the fix, green after)
tests/services/externalFormProvisioningService.spec.js: 8 new or changed cases. They cover a level on create for each of the three values, keeping a stored level on a repeat save, raising, explicitly lowering, dropping a stored0, and nominTruston an anonymous page. Before the fix: 8 red.tests/dialogs/ExternalFormAccessDialog.spec.js: 5 new cases. They cover the options offered, the level being sent, no public create grant, revoking the earlier grant, leaving the level alone, and choosing none. Before the fix: 5 red.tests/components/RegistrationFormEditorSignIn.spec.js: 3 cases, all red before the fix.tests/Unit/Service/RegistrationFormValidatorTest.php(4 new),RegistrationFormFragmentTest.php(schema declares the enum, not required) andRegistrationFormPresenterSignInTest.php(2 new). Before the fix: 4 red in the validator and fragment tests, 1 red in the presenter test.Verified
npx vitest runon the four touched specs: exit 0 (45 passed)vendor/bin/phpunit -c phpunit-unit.xml --filter RegistrationForm: exit 0 (70 tests)COMPOSER_PROCESS_TIMEOUT=0 composer check:strict: exit 1, only from gate-101 demo-data-coverage (inherited, see below). lint, phpcs, phpmd, psalm, phpstan and PHPUnit (1308 tests OK) are green. The first run flagged phpmd ExcessiveClassComplexity on RegistrationFormValidator (52 of 50), caused by this change; I fixed it in the second commit.npm run lint: exit 0npm run format: exit 0npm run test:l10n: exit 0npm run check:schema-l10n: exit 0. The two new schema strings are in en.json and nl.json, and the new UI strings are translated to Dutch too.npm run check:l10n-js: exit 0python3 parity_verify.py --strict openspec/parity/capabilities.json(hydra development): exit 1, only the inheritedunknown-cellslisting, the same as on development.Known gap, filed in portaliq
portaliq's
PortalFormTrustLeveltreats a declaredlowon a registration form as anonymous, while itsportalPagecontract treatslowas signed in. So a form set to "level low" is still served without a session until portaliq#731 is fixed.substantialandhighare enforced.Inherited findings
gate-101 demo-data-coverage is red on development: Application, ApplicationTemplate, ApplicationVersion, BuiltAppRoute and exportJob have no demo objects. The registrationForm demo data stays valid.
Fixes #935
🤖 Generated with Claude Code