Problem/Motivation

There is code in MenuTreeStorage to load menu tree elements by some properties, like 'route_name'.
The method MenuTreeStorage::loadByProperties() allows to pass in any kind of parameter.

This is problematic in case someone does not know which parameters to filter by and passes in a wrong one.
This could lead to invalid SQL queries.

Proposed resolution

Validate the passed in parameter, whether it actually exists, so you cannot produce invalid SQL.

Remaining tasks

User interface changes

API changes

Original report by @kgoel

In MenuTreeStorage.php
// @todo Only allow loading by plugin definition properties.

We need to add plugin definition property so we can throw an exception. Right now its using definitionFields() which is big array and using database query to load properties.

Comments

kgoel’s picture

tim.plunkett’s picture

Issue tags: +Plugin system

Can you expand on why this is needed or what it actually does?

dawehner’s picture

Issue summary: View changes

Updated, but I think this is nothing we should do. If you as a developer ask for a wrong name, this is just your fault.

pwolanin’s picture

Title: Allow loading by plugin definition properties » Only allow loading by plugin definition properties
Status: Active » Postponed
pwolanin’s picture

Status: Postponed » Active

ready to go ahead

cilefen’s picture

Status: Active » Needs review
StatusFileSize
new1.28 KB
tim.plunkett’s picture

+++ b/core/lib/Drupal/Core/Menu/MenuTreeStorage.php
@@ -635,11 +636,12 @@ protected function prepareLink(array $link, $intersect = FALSE) {
+        throw new InvalidPluginDefinitionException('An invalid plugin definition was specified: ' . $name);

Use sprintf or formatString, please

pwolanin’s picture

Status: Needs review » Needs work

I might consider using just \InvalidArgumentException instead of the plugin exception?

Also, the method doc needs to have an @throws added

cilefen’s picture

Status: Needs work » Needs review
StatusFileSize
new1.34 KB
dawehner’s picture

I think we should also expand the MenuTreeStorageTest to check that the exception is thrown.

cilefen’s picture

StatusFileSize
new1.07 KB
new2.54 KB
pwolanin’s picture

Status: Needs review » Needs work

Let's put the @throws on the interface, and leave this as @inheritdoc

Also, the exception message isn't quite right: 'An invalid plugin definition' should be more like 'An invalid property name'

tim.plunkett’s picture

Also, while you're at it:
Consider using @expectedException and @expectedExceptionMessage instead of the the try/catch.

cilefen’s picture

Status: Needs work » Needs review
StatusFileSize
new2.44 KB
new2.82 KB

@tim.plunkett Is @expectedException usable? It's not phpunit.

tim.plunkett’s picture

Oh, I missed that it was simpletest. Carry on!

pwolanin’s picture

Status: Needs review » Reviewed & tested by the community

Looks good.

alexpott’s picture

Status: Reviewed & tested by the community » Needs work
+++ b/core/modules/system/src/Tests/Menu/MenuTreeStorageTest.php
@@ -300,6 +300,24 @@ public function testSubtreeHeight() {
+    try {
+      $this->treeStorage->loadByProperties($properties);
+    }

We need a $this->fail('An invalid property name throws an exception.');

+++ b/core/modules/system/src/Tests/Menu/MenuTreeStorageTest.php
@@ -300,6 +300,24 @@ public function testSubtreeHeight() {
+      $this->pass(t('An invalid property name throws an exception.'));

We don't use t() assertion messages anymore.

cilefen’s picture

Status: Needs work » Needs review
StatusFileSize
new846 bytes
new2.89 KB
pwolanin’s picture

Status: Needs review » Reviewed & tested by the community

Thanks @cilefen

alexpott’s picture

Status: Reviewed & tested by the community » Needs review

Are we sure we want to do this? We don't do this in any of the other loadByProperties implementations - perhaps we're being overly defensive - yes we'll get an SQL error during development but that error will explain nicely to the developer that they've done a loadByProperties on a non-existing property - maybe I'm missing something?

diff --git a/core/modules/system/src/Tests/Menu/MenuTreeStorageTest.php b/core/modules/system/src/Tests/Menu/MenuTreeStorageTest.php
index d68ccb2..f24d54c 100644
--- a/core/modules/system/src/Tests/Menu/MenuTreeStorageTest.php
+++ b/core/modules/system/src/Tests/Menu/MenuTreeStorageTest.php
@@ -305,12 +305,13 @@ public function testSubtreeHeight() {
   public function testLoadByProperties() {
     $properties = array('foo' => 'bar');
     try {
+      $msg = 'An invalid property name throws an exception.';
       $this->treeStorage->loadByProperties($properties);
-      $this->fail('An invalid property name did not throw an exception.');
+      $this->fail($msg);
     }
     catch (\InvalidArgumentException $e) {
       $this->assertEqual('An invalid property name, foo was specified.', $e->getMessage());
-      $this->pass('An invalid property name throws an exception.');
+      $this->pass($msg);
     }
     $this->addMenuLink(1);
     $properties = array(array('menu_name' => 'tools'));

@cilefen the pass and fail messages should not be different. Perhaps what is above would be better.

pwolanin’s picture

@alexpott - so, yes we want to do this. Here's why: it should only be supported by the API to load using the definition properties. However, any given implementation may have a variety of additional properties/columns in the storage. We need to prevent people from making assumptions about the implementation.

dawehner’s picture

+++ b/core/lib/Drupal/Core/Menu/MenuTreeStorage.php
@@ -635,11 +635,12 @@ protected function prepareLink(array $link, $intersect = FALSE) {
+      if (!in_array($name, $this->definitionFields())) {
+        throw new \InvalidArgumentException(String::format('An invalid property name, @name was specified.', array('@name' => $name)));
+      }

Can we list the available fields in the exception?

cilefen’s picture

StatusFileSize
new2.07 KB
new3.21 KB

Includes @alexpott's and @dawehner's suggestions. As always, thank you for reviewing.

dawehner’s picture

Status: Needs review » Reviewed & tested by the community

Cool, thank you.

alexpott’s picture

Status: Reviewed & tested by the community » Fixed

Committed 25627b0 and pushed to 8.0.x. Thanks!

alexpott’s picture

Committed 25627b0 and pushed to 8.0.x. Thanks!

  • alexpott committed 25627b0 on 8.0.x
    Issue #2302165 by cilefen | kgoel: Only allow loading by plugin...
sun’s picture

Category: Task » Bug report
Status: Fixed » Needs work
+++ b/core/modules/system/src/Tests/Menu/MenuTreeStorageTest.php
@@ public function testLoadByProperties() {
+    $properties = array(array('menu_name' => 'tools'));
+    $links = $this->treeStorage->loadByProperties($properties);
+    $this->assertEqual('tools', $links[1]['menu_name']);

Sorry, but the test code is broken; bogus double-array:

-    $properties = array(array('menu_name' => 'tools'));
+    $properties = array('menu_name' => 'tools');

HEAD should fail, but it does not. Here's why:

Insufficient parameter validation. The validation in loadByProperties() performs a loose comparison; a non-empty array is always "in" an array that is not empty.

Rule of thumb: Always use a strict comparison for in_array() unless you have a very good reason to not use it:

-      if (!in_array($name, $this->definitionFields())) {
+      if (!in_array($name, $this->definitionFields(), TRUE)) {

In addition to that though, for some unknown reason, HEAD seems to return a result that happens to match the assertEqual() test (equally loose comparison), which I can't explain, but definitely must not happen.

cilefen’s picture

Status: Needs work » Needs review
StatusFileSize
new1.5 KB

@sun your attention to detail is appreciated. Here are the fixes you suggested.

sun’s picture

Status: Needs review » Reviewed & tested by the community

@cilefen: It wasn't me. :) This is really just one of many errors that would have been natively caught by PHPUnit: #2304461: KernelTestBaseTNG™

pwolanin’s picture

So, I think the other problem here is that all menu links are in tools by default, so it would be a better test to load one using a different menu name.

cilefen’s picture

StatusFileSize
new764 bytes
new1.69 KB

Added the test link to a different menu as per @pwolanin's suggestion and added an additional assert to be sure we are looking at the correct link.

pwolanin’s picture

Minor, but we should use a distinct string ID instead of 1.

cilefen’s picture

StatusFileSize
new832 bytes
new1.72 KB
pwolanin’s picture

Status: Reviewed & tested by the community » Needs review
StatusFileSize
new1.99 KB
new2.86 KB
new2.02 KB

The actual fail was the 0 to string compare, not nested array, so let's test that too.

This change to the test verifies that case fails without the strict comparison in the full patch.

The last submitted patch, 35: 2302165-test-only-34.patch, failed testing.

cilefen’s picture

Status: Needs review » Reviewed & tested by the community

@pwolanin Thanks.

alexpott’s picture

Status: Reviewed & tested by the community » Fixed

Committed a04820c and pushed to 8.0.x. Thanks!

  • alexpott committed a04820c on 8.0.x
    Issue #2302165 followup by cilefen, pwolanin | kgoel: Fixed Only allow...

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.