original title: "template_preprocess_menu_local_task() requires explicit plaining of link titles"
Problem/Motivation
\Drupal\Core\Menu\MenuLinkDefault::getTitle
has
return $this->t($this->pluginDefinition['title'], $args, $options);
and t()
calls SafeMarkup::set
so at this point the title is marked safe despite it might not be.
Proposed resolution
Change the YAML discovery of menu links so that we wrap text that's safe and translatable in a TranslationWrapper object. This way we can call t() on strings from YAML but not on strings in derivatives that might have definitions that come from user input
Follow-up in #2338081: Local Tasks, Actions, and Contextual links mark strings from derivatives (or alter hooks) as safe and translated to amend t() documentation to make it crystal clear that what gets passed into it must be a safe string that is not user input.
Remaining tasks
review
User interface changes
none
API changes
Modules implementing hook_menu_links_discovered_alter() may need to create TranslationWrapper() objects instead of simply setting a string title and description.
Data model changes
Change the menu link plugin definition and schema so the title and description may be a TranslationWrapper and serialized as such into the database, and remove the title context and options columns
Comment | File | Size | Author |
---|---|---|---|
#67 | interdiff.txt | 2.84 KB | dawehner |
#67 | 2532476-67.patch | 23.53 KB | dawehner |
#63 | increment.txt | 7.72 KB | pwolanin |
#63 | 2532476-63.patch | 23.56 KB | pwolanin |
| |||
#53 | interdiff.txt | 2.26 KB | pfrenssen |
Comments
Comment #1
dawehnerSo this is basically postponed on #2338081: Local Tasks, Actions, and Contextual links mark strings from derivatives (or alter hooks) as safe and translated
Comment #2
alexpottDiscussed with @xjm, @catch, @effulgentsia and @webchick and we agreed that this issue is critical because of the security concerns of passing user test into t().
Comment #3
pwolanin CreditAttribution: pwolanin as a volunteer and at Acquia commentedMaybe we should start rolling this patch based on the YAML discovery change from the other patch?
Comment #4
dawehnerWell, this is not as simple ... given that we need to discuss what we store in
{menu_tree}
potentially.Comment #5
pwolanin CreditAttribution: pwolanin as a volunteer and at Acquia commentedI don't think there is any really debate - we just need to store the serialized title & description.
just NR here for the bot.
The patch is on top of the latest from the other issue
Comment #7
dawehnerWell, I get that we need to store it, but this means that we can no longer query it. Does that mean we should throw an exception when we call MenuTreeParameter with a title?
Comment #8
dawehnerAh yeah I see, so we just need to clearly communicate that querying by it is pointless. What about throwing an exception?
In genearl this looks great and probably needs some custom test coverage to ensure it doesn't causes issues.
Comment #9
dawehnerJust some small bits of tests now.
Comment #10
pwolanin CreditAttribution: pwolanin as a volunteer and at Acquia commentedAh, so we should disallow loading by serialized properties?
Comment #11
dawehnerYeah, I mean it doesn't make sense at all, or at least it is not the expected behaviour, because people won't know that the serialized object is stored in there.
Comment #12
pwolanin CreditAttribution: pwolanin as a volunteer and at Acquia commentedI think this fixes some of the tests.
Comment #14
pwolanin CreditAttribution: pwolanin as a volunteer and at Acquia commentedThe other issue is committed, so we can proceed normally here.
Comment #16
pwolanin CreditAttribution: pwolanin as a volunteer and at Acquia commentedTrying to add an update function, but those tests still trying to unserialze invalid data.
Comment #18
dawehnerRegarding the test failures ... this happens when the update test ship with the OLD db.
I fear that we need to add some checks before we unserialize given that going to update.php might also trigger a menu rebuild.
Does that mean that update.php maybe should not rebuild the menu but just rebuild the router?
Are sure the update function can be that simple? Don't we need to update the values, as things from menu_link_content and maybe even other code
has entered data in there.
Comment #19
dawehnerOne idea, don't use the same value again but rather use title_serialized and description_serialized ... I think making that semantically more clear is also a good idea!
Comment #21
dawehnerMh, i'm not convinced that this approach is necessary the best one.
Comment #23
pwolanin CreditAttribution: pwolanin as a volunteer and at Acquia commented@dawehner - yes, we might need to serialize the existing values in the update function, but it seems that the update function is not working if they are not NULL but are strings.
I don't think the last patch is the right approach - we have other serialized fields in the schema already.
Comment #24
dawehner@pwolanin and myself talked and agreed that these two update path tests are wrong. They should setup their DB in a way that it has those test modules installed.
Comment #25
pwolanin CreditAttribution: pwolanin as a volunteer and at Acquia commentedSo, that looks better, but a few problems.
I don't see why we need to split into 2 update function.
There is no code to drop the 2 unused columns.
Also, we should not necessarily assume items using MenuLinkDefault are actually from YAML - though maybe that's true in core right now?
Comment #27
pwolanin CreditAttribution: pwolanin as a volunteer and at Acquia commentedLet's just serialze the title and description strings and not try to use SafeMarkup there. IF th links coem from YAML they should get fixed on the next rebuild.
Comment #28
dawehnerWhen we talked about it on saturday things seem so obvious but in reality it is not.
So one failure starts with the problem that some part of the test calls $this->rebuildAll(), which rebuilds everything from scratch. This obviously doesn't work
as long you haven't ran update.php
You can skip that rebuilding of course, but then many other things are not up to date.
Here is some progress but I actually think that #21 will result in less issues also on the actual site.
Comment #30
dawehnerI am actually more convinced now that the trickyness of the early upgrade path and the risk of having a triggered menu rebuild is too dangerous so we need to rename the key.
This should fix the failures of #21
Comment #31
Gábor HojtsyNot intended is it?
I think this would deserve some comment as to why it is done.
Should use a similar comment for description.
Also needs change records. I'll start on those ASAP.
Comment #33
pwolanin CreditAttribution: pwolanin as a volunteer and at Acquia commented@dawehner - I really disagree with changing the key. At the worst there are notices.
We need to just fix the DB dump
Comment #34
Gábor HojtsyCame up while reviewing the patch for a change notice: Should this still have StringTranslationTrait on MenuLinkDefault? It was removed from the other link classes in #2338081: Local Tasks, Actions, and Contextual links mark strings from derivatives (or alter hooks) as safe and translated.
Comment #35
Gábor HojtsyAdded change notice draft at MenuLinkDefault now uses a TranslationWrapper for title and description, title_arguments removed https://www.drupal.org/node/2540620 -- may need more change notices later depending on the extent of change done.
Comment #36
dawehnerThis is not true, we also need to fix tthe update path test itself, this is a rabit hole, just saying.
Also notices is something we should avoid ... especially if its just about renaming properties.
We are now storing something entirely different.
Comment #37
pwolanin CreditAttribution: pwolanin at Acquia commentedStarting back from #16, this interdiff is relative to that but incorporates some of dawehner's later changes selectively.
Also, suppresses the notices specifically in the 2 upgrade tests since they are an artifact of the rebuild being run before the update function. Discussed this approach with dawehner and xjm in IRC as a possible minimum fix to move forward rather than trying to rewrite the tests.
Comment #38
pwolanin CreditAttribution: pwolanin as a volunteer and at Acquia commentedComment #39
pwolanin CreditAttribution: pwolanin as a volunteer and at Acquia commentedComment #42
effulgentsia CreditAttribution: effulgentsia at Acquia commentedLooks like this has tests and an upgrade path, so removing those tags, but the upgrade path needs a test, so tagging for that.
If these notices only showed up as an artifact of the test runner, I think this would be fine. But I tried manually testing update.php (via the web ui) after applying this patch, and the notices happened in that case too (not to the screen, but to /admin/reports/dblog), so I think we need to fix that, either by figuring out where a rebuild is happening that shouldn't be, or by temporarily putting an
@
in front of the pre-or-during-update-triggeredunserialize
calls with an @todo linking to an issue (perhaps #2540416: Move update.php back to a front controller) for removing that once we figure out how to.I manually tested an update that included a menu_link_content link as well, and after the update, that one wasn't serialized. Perhaps because
$sandbox['current'] += 50;
needs to be outside of the foreach loop?Comment #43
dawehnerGiven that we talk about a security issue here, let's ensure we have some test coverage for it.
Didn't had a look yet at the notices mentioned in #42.1
Comment #44
catchI can reproduce the notice, happens at the router rebuild at the end of the update. #42.2 might explain that if not every link actually gets updated.
Comment #45
dawehnerI manually tested the latest iteration and there I no longer see the notice in the logs. They should also come up in the test, so we are fine here, at least regarding that problem.
Comment #46
stefan.r CreditAttribution: stefan.r commentedspelling nits
may be
did not exist
Ensures
TranslationWrapper.
Comment #47
pfrenssenJust a quick dreditor review. Looks good overall.
Any particular reason
array_flip()
is used in combination withisset()
instead of simply usingin_array($column, $serialized)
without flipping the array?Or even more simply
in_array($column, $this->serializedFields())
?Is this a performance optimization?
Good to test this like this.
Start with a capital letter.
db_field_exists()
is deprecated. Should be replaced with:Database::getConnection()->schema()->fieldExists($table, $field);
This relies on the fact that
unserialize()
throws a notice when a string is not unserializable. This looks a bit weird though since it seems like nothing is asserted.Maybe add a line of documentation explaining this?
Yuck :D
Double semicolon.
Two empty lines here.
Grammar: ".. did not exist yet .."
Should be
{@inheritdoc}
.Two empty lines.
Comment #48
pfrenssenComment #49
dawehnerThank you for your reviews @stefan.r and @pfrenssen
This is certainly micro opt, there aren't that many conditions, so I doubt you can actually matter, given that this function is a potential one time operation.
Comment #50
Gábor HojtsyAs far as I see all reviews have been addressed and there are no outstanding concerns.
This is pretty bad but if we have no better way, its better to get this done and deal with this ugliness.
Comment #51
Gábor HojtsyLooking at my change notice draft from earlier, it looks good still as well vs. the current patch AFAIS: https://www.drupal.org/node/2540620
Comment #52
alexpottLooks good - some small nits:
I don't think the string cast here is correct. It makes the next assertion pointless.
$this->assertEscaped()?
This method is deprecated, use $schema->tableExists() instead.
Comment #53
pfrenssenFixed remarks from #52.
Comment #54
Gábor HojtsyYay, thanks!
Comment #55
dawehnerNice I didn't knew that this exists.
Comment #56
pwolanin CreditAttribution: pwolanin as a volunteer and at Acquia commentedWhy did we remove the array_flip optimization and change from isset() to in_array()?
That's a common and useful optimization when loading a lot of links
Comment #57
pwolanin CreditAttribution: pwolanin as a volunteer and at Acquia commentedNever mind, I was thinking of the usage in another method. In this method it doesn't really matter.
Comment #58
dawehnerYeah I wanted to answer that this is about conditions, so you really should not have tons of them. In case you have, you have bigger problems :)
Comment #59
alexpottAren't we potentially throwing data away here? Should we warn the user and back it up if there is data here? Core was not using it but contrib/custom could be.
I'm not sure about this so setting to needs review for more opinion.
Comment #60
dawehnerWell yes theoretically this is indeed true, but on the other hand I think this would have been mainly used by the static yml files, which will get all the information from the
drupal_flush_all_caches()
at the end.We could create one gigantic KV entry with all that information though, but the thing is, it is not entirely trivial to figure out which entries are from static yml files, so we might have to store all of them?
Comment #61
Gábor Hojtsy@alexpott: In core at least only statically defined items had arguments and context, eg. ViewsMenuLink does not allow entering a context (or argument) with titles. It cannot be ruled out that some contrib/custom did it.
Comment #62
pwolanin CreditAttribution: pwolanin as a volunteer and at Acquia commented@alexpott - the only core use is data from YAML files, and as dawehner says, we will get that information saved again in the rebuild at the end of the update.
We could add a test for that perhaps?
Comment #63
pwolanin CreditAttribution: pwolanin at Acquia commentedHere's the test change + fixes for 3 alter hooks effulgentsia pointed out to me as needing to use TranslationWrapper objects + fix to the api doc.
Comment #64
effulgentsia CreditAttribution: effulgentsia at Acquia commentedSo, anything with {menu_tree}.discovered=1 gets overwritten or purged during MenuTreeStorage::rebuild() anyway. Which means the data loss from the update function would be only for the following conditions:
If we only wanted to worry about the 2nd case, then I think we could do something like backup data (into
\Drupal::keyValue('update_backup')
) for links we encounter that have discovered=0 and non-empty title_arguments or title_context.If we wanted to worry about the 1st case, we'd probably need to back up all links with non-empty title_arguments or title_context.
I think a case could also be made that both cases are edge case enough, that such data loss is acceptable during a beta. The site should have database backups anyway, and could then deal with merging the data from those database backups. Then again, given there's few links with arguments/context anyway, I don't see much of a downside to backing all of them up in the KV.
Comment #65
dawehnerI have one contrib project: menu_link_config, but as it works like menu_link_content it does not support arguments/contexts.
In general I agree with peter that the probably for arguments/contexts are really low, especially because contexts just make sense for t().
Comment #66
Gábor HojtsyAgreed with @dawehner!
Comment #67
dawehnerLet's at least not make the readability of the test worse than it has to be.¬
Comment #68
alexpottCommitted 63596b2 and pushed to 8.0.x. Thanks!
Comment #70
Gábor HojtsyPublished https://www.drupal.org/node/2540620 too.
Comment #71
mradcliffeSomething is going wrong with these serialized strings and they are not the correct length on PostgreSQL. This looks like the oject that is serialized is not fully serialized for some reason. I'll open a follow-up since this is several days old at this point.
Comment #72
pwolanin CreditAttribution: pwolanin at Acquia commented@mradcliffe - it sounds like the update hook didn't run? The prior column was 255 characters of text.
Comment #73
neclimdulThis broke --die-on-fail for all update tests. #2549045: Update Tests can't run with --die-on-fail
Comment #74
Corregidor CreditAttribution: Corregidor commented@mradcliffe @pwolanin - It looks like system_update_8001 fails in postgresql as after the update, the title column is still of type varchar(255). Its data, however, appears to be serialized. @mradcliffe - did you create a new ticket to follow-up on this?
Comment #75
Corregidor CreditAttribution: Corregidor commentedAs a result, the serialized TranslationWrapper objects are cut off. Almost all titles and descriptions of menu_tree items for the Admin menu look like this:
It's not the size of the column that cuts off the serialization. I believe it's a null byte. Priming the column by manually converting it to a bytea (Postgresl's blob) seems to ensure that all the serializations are saved during the update.
Comment #76
pwolanin CreditAttribution: pwolanin at Acquia commentedA lot of the postgres update hook-related core tests fail, so possibly this is a known bug?
Comment #77
neclimdulIt is. I can't seem to find the issue but I remember seeing it.
The problem has to do with a long standing issue that can't just convert varchar to blob effectively in postgres. Implied casting or something.
Comment #78
mradcliffe@epong, follow-up is #2548725: Fix regression to menu serialization in upgrade path that causes thousands of errors in PostgreSQL.
Comment #79
catchThis is why it broke postgres:
The error is slightly different on MySQL, so the hack didn't catch it.