Headline
CVE-2023-41336: Symfony UX Autocomplete Documentation
ux-autocomplete is a JavaScript Autocomplete functionality for Symfony. Under certain circumstances, an attacker could successfully submit an entity id for an EntityType
that is not part of the valid choices. The problem has been fixed in symfony/ux-autocomplete
version 2.11.2.
Autocomplete <select>Edit this page
Autocomplete <select>
Transform your EntityType, ChoiceType or any <select> element into an Ajax-powered autocomplete smart UI control (leveraging Tom Select):
Installation
Before you start, make sure you have StimulusBundle configured in your app.
Then install the bundle using Composer and Symfony Flex:
If you’re using WebpackEncore, install your assets and restart Encore (not needed if you’re using AssetMapper):
Usage in a Form (without Ajax)
Any ChoiceType or EntityType can be transformed into a Tom Select-powered UI control by adding the autocomplete option:
That’s all you need! When you refresh, the Autocomplete Stimulus controller will transform your select element into a smart UI control:
Usage in a Form (with Ajax)
In the previous example, the autocomplete happens "locally": all of the options are loaded onto the page and used for the search.
If you’re using an EntityType with many possible options, a better option is to load the choices via AJAX. This also allows you to search on more fields than just the “displayed” text.
To transform your field into an Ajax-powered autocomplete, you need to create a new “form type” class to represent your field. If you have MakerBundle installed, you can run:
Or, create the field by hand:
There are 3 important things:
- The class needs the #[AsEntityAutocompleteField] attribute so that it’s noticed by the autocomplete system.
- The getParent() method must return ParentEntityAutocompleteType.
- Inside configureOptions(), you can configure your field using whatever normal EntityType options you need plus a few extra options (see Form Options Reference).
After creating this class, use it in your form:
Caution
Avoid passing any options to the 3rd argument of the ->add() method as these won’t be used during the Ajax call to fetch results. Instead, include all options inside the custom class (FoodAutocompleteField).
Congratulations! Your EntityType is now Ajax-powered!
Styling Tom Select
In your assets/controllers.json file, you should see a line that automatically includes a CSS file for Tom Select which will give you basic styles.
If you’re using Bootstrap, set tom-select.default.css to false and tom-select.bootstrap5.css to true:
To further customize things, you can override the classes with your own custom CSS and even control how individual parts of Tom Select render. See Tom Select Render Templates.
Form Options Reference
All ChoiceType, EntityType and TextType fields have the following new options (these can also be used inside your custom Ajax autocomplete classes, e.g. FoodAutocompleteField from above):
autocomplete (default: false)
Set to true to activate the Stimulus plugin on your select element.
tom_select_options (default: [])
Use this to set custom Tom Select Options. If you need to set an option using JavaScript, see Extending Tom Select.
options_as_html (default: false)
Set to true if your options (e.g. choice_label) contain HTML. Not needed if your autocomplete is AJAX-powered.
autocomplete_url (default: null)
Usually you don’t need to set this manually. But, you could manually create an autocomplete-Ajax endpoint (e.g. for a custom ChoiceType), then set this to change the field into an AJAX-powered select.
no_results_found_text (default: ‘No results found’)
Rendered when no matching results are found. This message is automatically translated using the AutocompleteBundle domain.
no_more_results_text (default: ‘No more results’)
Rendered at the bottom of the list after showing matching results. This message is automatically translated using the AutocompleteBundle domain.
For the Ajax-powered autocomplete field classes (i.e. those whose getParent() returns ParentEntityAutocompleteType), in addition to the options above, you can also pass:
searchable_fields (default: null)
Set this to an array of the fields on your entity that should be used when searching for matching options. By default (i.e. null), all fields on your entity will be searched. Relationship fields can also be used - e.g. category.name if your entity has a category relation property.
security (default: false)
Secures the Ajax endpoint. By default, the endpoint can be accessed by any user. To secure it, pass security to a string role (e.g. ROLE_FOOD_ADMIN) that should be required to access the endpoint. Or, pass a callback and return true to grant access or false to deny access:
filter_query (default: null)
If you want to completely control the query made for the "search results", use this option. This is incompatible with searchable_fields:
max_results (default: 10)
Allow you to control the max number of results returned by the automatic autocomplete endpoint.
min_characters (default: 3)
Allow you to control the min number of characters to load results.
preload (default: focus)
Set to focus to call the load function when control receives focus. Set to true to call the load upon control initialization (with an empty search).
Using with a TextType Field
All of the above options can also be used with a TextType field:
This <input> field won’t have any autocomplete, but it will allow the user to enter new options and see them as nice “items” in the box. On submit, all of the options - separated by the delimiter - will be sent as a string.
You can add autocompletion to this via the autocomplete_url option - but you’ll likely need to create your own custom autocomplete endpoint.
Customizing the AJAX URL/Route
2.7
The ability to specify the route was added in Twig Components 2.7.
The default route for the Ajax calls used by the Autocomplete component is /autocomplete/{alias}/. Sometimes it may be useful to customize this URL - e.g. so that the URL lives under a specific firewall.
To use another route, first declare it:
Then specify this new route on the attribute:
Extending Tom Select
The easiest way to customize Tom Select is via the tom_select_options option that you pass to your field. This works great for simple things like Tom Select’s loadingClass option, which is set to a string. But other options, like onInitialize, must be set via JavaScript.
To do this, create a custom Stimulus controller and listen to one or both events that the core Stimulus controller dispatches:
Note
The extending controller should be loaded eagerly (remove /* stimulusFetch: ‘lazy’ */), so it can listen to events dispatched by the original controller.
Then, update your field configuration to use your new controller (it will be used in addition to the core Autocomplete controller):
Or, if using a custom Ajax class, add the attr option to your configureOptions() method:
Advanced: Creating an Autocompleter (with no Form)
If you’re not using the form system, you can create an Ajax autocomplete endpoint and then initialize the Stimulus controller manually. This only works for Doctrine entities: see Manually using the Stimulus Controller if you’re autocompleting something other than an entity.
To expose the endpoint, create a class that implements Symfony\UX\Autocomplete\EntityAutocompleterInterface and tag this service with ux.entity_autocompleter, including an alias option:
Thanks to this, your can now autocomplete your Food entity via the ux_entity_autocomplete route and alias route wildcard:
Usually, you’ll pass this URL to the Stimulus controller, which is discussed in the next section.
Manually using the Stimulus Controller
This library comes with a Stimulus controller that can activate Tom Select on any select or input element. This can be used outside of the Form component. For example:
That’s it! If you want the options to be autocompleted via Ajax, pass a url value, which works well if you create a custom autocompleter:
Note
If you want to create an AJAX autocomplete endpoint that is not for an entity, you will need to create this manually. The only requirement is that the response returns JSON with this format:
for using Tom Select Option Group the format is as follows
Once you have this, generate the URL to your controller and pass it to the url value of the stimulus_controller() Twig function, or to the autocomplete_url option of your form field. The search term entered by the user is passed as a query parameter called query.
Beyond url, the Stimulus controller has various other values, including tomSelectOptions. See the controller.ts file for the full list.
Unit testing
When writing unit tests for your form, using the TypeTestCase class, you consider registering the needed type extension AutocompleteChoiceTypeExtension like so:
Known Issue when using with Live Component
You can use autocomplete inside of a Live Component: the autocomplete JavaScript widget should work normally and even update if your element changes (e.g. if you add or change <option> elements. Internally, a MutationObserver inside the UX autocomplete controller detects these changes and forwards them to TomSelect.
However, if you use the multiple option, due to complexities in TomSelect, the autocomplete widget will work, but it will not update if you change any options. For example, if your change the “options” for a select during re-render, those will not update on the frontend.
Related news
### Impact Under certain circumstances, an attacker could successfully submit an entity id for an `EntityType` that is *not* part of the valid choices. Affected applications are any that use: * A custom `query_builder` option to limit the valid results; AND * An `EntityType` with `'autocomplete' => true` or a custom [AsEntityAutocompleteField](https://symfony.com/bundles/ux-autocomplete/current/index.html#usage-in-a-form-with-ajax). Under this circumstance, if an id is submitted, it is accepted even if the matching record would not be returned by the custom query built with `query_builder`. ### Patches The problem has been fixed in `symfony/ux-autocomplete` version 2.11.2. ### Workarounds Upgrade to version 2.11.2 or greater of `symfony/ux-autocomplete` or perform extra validation after submit to verify the selected option is valid.