Repository navigation
Release v1.6.0
v1.6.0
Changelog
UiKit page contract, Playwright e2e + screenshots, 100% coverage; SEC-004 hardening: open redirect fix (GHSA), http(s) privacy URL, controller-class guard, CSRF/roles compile checks, length caps, rate limiting See UPGRADING.
Security
- Open redirect in the admin locale switch (CWE-601, affects 1.0.0–1.5.1).
{prefix}/locale/{locale}?redirect=
accepted any value starting with/but not//:/%5Cevil.example/,/%09/evil.example/or%5C%5Cevil.example
were followed by browsers to another host. Theredirectparameter and the same-hostRefererfallback now go
throughSecurity\SafeRelativePath(single leading/, no backslash / whitespace / control character, no./
..segment even percent-encoded, insideweb_ui.path_prefix). - The admin access guard keys on the admin controller classes, not only on route names (a host route
name_prefix,
fragments and sub-requests are now guarded). - The admin UI refuses to compile without form CSRF protection and a CSRF token manager;
symfony/security-csrfmoves
torequire. An emptysecurity.access_roleswith the default checker, andsecurity.allow_unauthenticatedin the
prodenvironment or in any non-debug kernel outsidetest*environments, fail the container compilation. - Privacy policy URL:
http(s)only (Urlconstraint,httpsdefault protocol); a storedjavascript:/data:value
is no longer rendered as a link. - Retention:
retentionDaysis validated (0 to 3650) and the cleanup skips forms with 0 or a negative value (a
negative value used to make every submission expire). - Public form: values are length-capped (
public_field_max_length, text 255, textarea 10 000, phone 32), every POST
counts against the rate limit (valid or not), andpublic_submission_rate_limit.limiteraccepts a
symfony/rate-limiterfactory for atomic counting. - Consent-label sanitizer drops comments, processing instructions and CDATA and rejects
/\hostlinks. - Field wizard: a tampered hidden field type is a validation error (no 500); field names must match
^[a-z][a-z0-9_]*$and cannot begdpr_consent/_token(checked on every step, including the hidden name of the
later steps); file fields are refused without a
file_upload.service; the notification e-mail is validated; admin name / slug / URL lengths match the columns. - Flex recipe: the separate
security_nowo_contact_form.yaml(a secondaccess_controllist, refused by Symfony) is
replaced by a post-install note.
Added
- UiKit page contract (REQ-UI-003). The admin pages extend the UiKit scaffolds —
page/list(contact forms,
fields, submissions),page/form(contact form),page/wizard(field wizard over the Symfony FormFlow) and
page/show(submission) — and fill data blocks only;Ui\ContactFormPageFactorybuilds the view models (page
headers with back links, columns, row actions,Pagination,EmptyState, detail sections, wizard steps) and
Ui\ContactFormPagesresolves the layouts and per-page templates. The public contact form is a unique page on the
UiKit guest layout. Contract testtests/Integration/Contract/PageContractTest.php(UiKitPageContractTestCase,
strict missing-translation rule) andLegacyLayoutBridgeTest. - Delete actions on the contact form and field lists, in the contact form delete zone and in the submission header,
behind the UiKit confirm<dialog>(POST + CSRF-only FormKit form); the routes existed but had no button. - Canonical configuration keys:
web_ui.path_prefix,web_ui.public_layout_template,web_ui.templates.<page>
(form_index,form_form,field_index,field_form,submission_index,submission_show,public_form). - Demo: UiKit host layouts (
templates/layouts/), seeded submissions inbox (app:seed-contact-demo [--reset-submissions]), Playwright e2e (make test-e2e, also inrelease-check) and WebP README screenshots
(make demo-screenshots) — REQ-DEMO-013. - Docs:
docs/TEMPLATES.md(pages → scaffolds, unique page, per-page overrides, REQ-TWIG-001 freeze rule, upgrade-safe
alternatives, recovery procedure and<subpath>table); USAGE / CONFIGURATION / INSTALLATION updated.
Changed
web_ui.layout_templatedefaults tonull(=nowo_ui_kit.layouts.admin); the CSS stack, icon set and row-action
display are inherited fromnowo_ui_kit. The FormKitcontact_formprofile seed is framework neutral
(nowo-ui-input, no row classes) and setsconvention_requires_translation: true; the admin templates no longer
forcebootstrap_5_layout.html.twig— the host form theme applies.ContactPhoneTypeand its form theme carry no Bootstrap classes (.nowo-contact-form-phone,role="group").ContactPhoneInputAvailabilitychecks that the PhoneInputBundlePhoneTypeis a form type and accepts a forced
answer (new ContactPhoneInputAvailability(true|false)); a phone field set to the Phone Input widget falls back to the
Symfony prefix widget when the type is missing, instead of disappearing from the form.nowo-tech/ui-kit-bundle^2.15.3,nowo-tech/form-kit-bundle^2.13.3.- PHPUnit: native deprecation gate (
failOnDeprecation,ignoreSuppressionOfDeprecations,
ignoreIndirectDeprecations),failOnRiskyandfailOnWarning(REQ-SF-005); the 21 risky integration tests (error
handlers removed intearDown()) are fixed. - Coverage: no
<exclude>left inphpunit.xml.dist; the gate is 100% PHP lines inmake test-coverage,
composer coverage-checkand the CI coverage job (REQ-TEST-003).
Deprecated
admin_route_prefix(useweb_ui.path_prefix),web_ui.css_framework/web_ui.icon_set(setnowo_ui_kit.*).- A
web_ui.layout_templatethat does not render the UiKit canonical blocks is wrapped in the
admin/_legacy_layout.html.twigbridge with a deprecation (removed in 2.0). - Twig globals
nowo_contact_form_layout_template,nowo_contact_form_css_framework,nowo_contact_form_icon_set
(the pages no longer read them).
Removed
- Local chrome:
admin/layout.html.twig(loaded Bootstrap from a CDN),admin/base.html.twig(inline locale-switch
andwindow.confirm()script),admin/_pagination.html.twig,admin/_locale_switch.html.twig. The interface
language switch moves to the host layout (routenowo_contact_form_admin_switch_localeunchanged).
Fixed
- Field wizard: editing an existing field saved nothing (the FormFlow returns the session copy; it is now applied to
the managed field), and the step data hooks never ran because Symfony does not dispatch*_SET_DATAon
inherit_datasteps — the select option labels per locale were missing, and editing a phone / select field showed
empty prefixes / machine names. The flow now hands the field to each step (ContactFormFieldStepDataInterface). - Contact form admin: a blank name, slug or title is a validation error instead of a 500 (
TypeErroron the setter). - Placeholder convention keys (
contact_form.name.placeholder, …) no longer leak as raw text into the admin forms. - The admin interface language also applies under a custom
web_ui.path_prefix(route-name match instead of the
hard-coded/admin/contact-formspath).
Template contract
| Page | Template | Scaffold | Data blocks |
|---|---|---|---|
| Contact forms | admin/form/index.html.twig |
page/list |
nowo_ui_list_row |
| Contact form | admin/form/form.html.twig |
page/form |
nowo_ui_form_body |
| Fields | admin/field/index.html.twig |
page/list |
nowo_ui_list_row |
| Field wizard | admin/field/form.html.twig |
page/wizard |
nowo_ui_wizard_step |
| Submissions | admin/submission/index.html.twig |
page/list |
nowo_ui_list_row |
| Submission | admin/submission/show.html.twig |
page/show |
— |
| Public form (unique) | public/contact_form.html.twig |
guest layout | nowo_ui_page_header, nowo_ui_content |
The page variable is page (UiKit view model); the 1.x variables forms, fields, submissions, page (int),
pages, total, pageSize, form, flow, deleteForm are gone (contactForm, field, isNew, currentStep,
submission remain).
What's Changed
- chore(deps): bump nowo-tech/ui-kit-bundle from 2.6.1 to 2.7.0 by @dependabot[bot] in #46
- feat: UiKit page contract, demo e2e + screenshots, 100% coverage gate (REQ-UI-003, REQ-DEMO-013) by @HecFranco in #47
- fix(security): admin locale-switch open redirect and input hardening (SEC-004) by @HecFranco in #48
Full Changelog: v1.5.1...v1.6.0