http://api.drupal.org/api/function/block_custom_block_delete/7 is implicitly assuming that $module is 'block' (only custom blocks created by the admin within block module can be deleted), but it doesn't verify this.

The assumption that $module == 'block' is in the call to
http://api.drupal.org/api/function/block_custom_block_get/7

So basically if you went to the delete URL with a different block (by typing in the URL -- the block module doesn't put up a link except for its own blocks), block module would still try to delete one of its own blocks.

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

glipay’s picture

Assigned: Unassigned » glipay
Status: Active » Needs review
FileSize
959 bytes

added a check to make sure its a custom block.

jhodgdon’s picture

I am not sure that drupal_not_found(); exit(); is the best response. Probably it would be better to set an error message and return to the block admin page?

jhodgdon’s picture

Also, maybe the return value of block_custom_block_get() should be checked, and if there's no result, put up an error message and return to the block admin page?

As a note, the submit function returns to the block admin page with a success message normally:
http://api.drupal.org/api/function/block_custom_block_delete_submit/7
And the cancel destination for the "are you sure" is also admin/structure/block:
http://api.drupal.org/api/function/block_custom_block_delete/7

glipay’s picture

FileSize
1.14 KB

ok, here's another a patch then

jhodgdon’s picture

Status: Needs review » Needs work

Still not quite right. As you can see from http://api.drupal.org/api/function/block_custom_block_get/7, that function doesn't receive the $module as input, so it's just trying to look up $block->delta as a BID.

So we need a check before that function even gets called to make sure $module == 'block'.

glipay’s picture

Status: Needs work » Needs review
FileSize
1.21 KB

ok, here's a new patch

Damien Tournoud’s picture

Status: Needs review » Needs work

Please, this is a form callback, and those should not have side-effects.

This needs to be handled in the menu system, as a standard access callback.

glipay’s picture

Status: Needs work » Needs review

sorry, this is already an established pattern used in core, see locale_languages_delete_form() and comment_multiple_delete_confirm() for example. if you want to change the pattern, fine, go create another issue, but I will not be held to higher standards than current drupal core is.

glipay’s picture

bump?

jhodgdon’s picture

Priority: Normal » Major

Either way this is done, I think the lack of validation checking (which could be in block_menu() or in the form as this patch does) is a major problem.

Actually... Since only custom blocks can be deleted with this form, why not change block_menu() so that the router path is
'admin/structure/block/manage/block/%/delete' instead of 'admin/structure/block/manage/%/%/delete', and change the form so that the module is not an input? The form won't work correctly unless module == 'block', so why even have that as an input?

Damien Tournoud’s picture

Status: Needs review » Needs work

Existing bad examples never justified putting more bad examples in core.

jhodgdon’s picture

Agreed on that! Let's try #10 suggestion.

glipay’s picture

Status: Needs work » Needs review
FileSize
6.62 KB

at first I tried the 'admin/structure/block/manage/block/%/delete', but that had the unwanted side effect of falling back to the configure page when an invalid block was tried. so I changed it to use a load function like the rest of the menu system does. this way, if the load fails, a normal page not found error is given to the user. I made a similar change to the configure page so that it would work in the same way. I also discovered a bug in the menu system while I was at it, since it appears that 'load arguments' are not properly inherited by menu items of type MENU_DEFAULT_LOCAL_TASK the way callback arguments and access arguments are. I fixed all the problems and created a new patch. thanks for all the feedback!

Damien Tournoud, do you think it would make sense to create bug reports for those other functions, then?

glipay’s picture

also note that without this patch if you went to a configure page for a nonexistant block, you would get a generic "Configure Block" form, but with the patch, you get a page not found error, which is the expected behavior.

Status: Needs review » Needs work

The last submitted patch, block.patch, failed testing.

glipay’s picture

Status: Needs work » Needs review
FileSize
7.38 KB

fixed statistics test, it was depending on the old (undocumented, weird) behavior of block_load(). this one should pass

jhodgdon’s picture

Ummm.... Is this not possible to do without quite so many API changes, at this stage of D7, and without a patch to menu.inc?

The new hook_menu structure is a bit strange looking too, though I see why you have made those choices... but I'm not sure it was really necessary to introduce the magic %block and %block_custom_block (especially the first one -- what does that actually have to do with this issue?).

glipay’s picture

I think the changes are necessary in order to do it correctly. there is really only one api change here, which is to rename block_custom_block_get() to block_custom_block_load(), which not only brings it more in line with other core functions, but also allows us to use it in hook_menu(). the other changes, such as the change to menu.inc, are simply bug fixes. there is no "magic" that goes on here, only normal loading of objects handled through the menu system. this is the only way I am aware of for a page to give a "page not found" error without resorting to an approach like the one in #6 that Damien Tournoud did not like. the %block change was a bug fix because as I explained above the block configure form, like the block deletion form, makes an unchecked assumption (in this case, simply assuming that the block exist). if you have another solution to achieve the same behavior without using the logic Damien Tournoud objected to, I would be happy to hear it, but I think this is the simplest, easiest, and best way to fix this bug.

jhodgdon’s picture

Status: Needs review » Needs work
Issue tags: +API change

Maybe Damien Tournoud can comment too?

I have a few other comments.

a) Looking at the menu.inc hunk of the patch:

+        if (($item['type'] == MENU_DEFAULT_LOCAL_TASK) && !isset($item['load arguments']) && isset($parent['load arguments'])) {
+          $item['load arguments'] = $parent['load arguments'];
+          $item['load_functions'] = $parent['load_functions'];
+        }
         // Same for page callbacks.
         if (!isset($item['page callback']) && isset($parent['page callback'])) {

Why do you need to check that the type is MENU_DEFAULT_LOCAL_TASK? I don't think any of the other "inherit from parents" checks in this function do that.

In any case, this is new behavior, which needs to be documented in the function header of this function, or actually I think it needs to be documented in hook_menu(), or maybe both places?

b) As far as API changes, you have changed the names and arguments of several functions. So I'm adding a tag, so it can be announced if this patch is accepted.

c) Here's another problem:

-  $items['admin/structure/block/manage/%/%'] = array(
+  $items['admin/structure/block/manage/%block/%'] = array(
     'title' => 'Configure block',
     'page callback' => 'drupal_get_form',
     'page arguments' => array('block_admin_configure', 4, 5),
     'access arguments' => array('administer blocks'),
+    'load arguments' => array(5),
     'file' => 'block.admin.inc',
   );

You need to change the page arguments. block_admin_configure doesn't take these args any more.

d) I also don't understand the logic of this:

  *
  * @param $bid
  *   ID of the block to get information for.
+ * @param $module
+ *   The module that defines the block. If this is anything besides 'block',
+ *   this function will return FALSE, because the block would not be custom.
  * @return
  *   Associative array of information stored in the database for this block.
  *   Array keys:
@@ -471,7 +476,10 @@ function _block_rehash($theme = NULL) {
  *   - body: Block contents.
  *   - format: Filter ID of the filter format for the body.
  */
-function block_custom_block_get($bid) {
+function block_custom_block_load($bid, $module = 'block') {
+  if ($module != 'block') {
+    return FALSE;
+  }
   return db_query("SELECT * FROM {block_custom} WHERE bid = :bid", array(':bid' => $bid))->fetchAssoc();
 }

If $module must be 'block', then why even include it in the function as an argument at all? Why not just put 'block' explicitly into the hook_menu() in the patch, rather than having it be [module], if 'block' is the only value of [module] that would work?

Damien Tournoud’s picture

You are slightly over-thinking this. It doesn't matter if an invalid URL results in a fallback to the configure page when an invalid block. I like jhodgdon's suggestion fo hardcoding 'block' in there.

About the Menu API change: it's arguably a good idea, but it really highlight a limitation of the current API that load arguments (the 'load arguments' key of the menu definition) are separated from the loader definition itself (which is the '%xxx' stuff in the path). It was already the case in D6, and sadly it's probably too late to fix that properly in D7.

glipay’s picture

Status: Needs work » Needs review
FileSize
7.97 KB

I checked for MENU_DEFAULT_LOCAL_TASK because it would be weird if every menu item started inherting the load arguments of its parent. the same thing is checked for inheritance of access callbacks and access arguments, so this is not without precedent. it seemed weird to document the load arguments inheritance in the otherwise bare documentation at the top of _menu_router_build(), so I just added it to the hook_menu() documentation instead.

it is find that you have added that tag to this issue, but I do not think this is a very big API change; in fact, only two functions ever called block_custom_block_get() in all of drupal.

you are right, the wrong page arguments were being used (though the function was still working). I fixed that problem, thank you.

I also made the placeholder in the menu item into a simple 'block' as both jhodgdon and Damien Tournoud suggested. I do not agree with them but they know more about this than me. I just thought that it would be good for the behavior to be consistent when trying to delete a nonexistent block; I thought a page not found error at all times was preferable to a page not found error sometimes and a fallback to the block configuration page other times. I do not understand why they do not agree, but I have still rerolled the patch as they requested.

jhodgdon’s picture

Given that both Damien and I are uncomfortable with the changes to menu.inc, I think we need a patch that doesn't require that change. If you want to make that change to the menu system, it should be done on its own issue, so it's done right and consistently, not as part of a change to the block module.

Regarding the API change tag, it doesn't matter if it's a big change or a small change. It's still a change to documented functions, and it needs to be recorded, documented, and announced.

glipay’s picture

FileSize
5.89 KB

I created another issue for the menu.inc changes at #869224: Menu router items of type "default local task" should inherit load arguments. removed those changes from this patch, and added a redeclared 'load arguments' key so that this patch would work without the change to menu.inc. when/if that change is made, the redeclaration of the load arguments can be moved.

I'm sorry, jhodgdon, I did not mean to challenge the legitimacy of the api change tag, I was simply commenting on how it was not a very big api change so perhaps required less worry about throwing off module developers. thank you, jhodgdon and Damien Tournoud, for taking the time and effort to review my patches.

jhodgdon’s picture

#23: block.patch queued for re-testing.

jhodgdon’s picture

Oh gosh, this is still sitting here. It's likely still a bug, and although an API change, probably serious enough a bug to warrant getting in... Sigh.

jhodgdon’s picture

#23: block.patch queued for re-testing.

jhodgdon’s picture

Status: Needs review » Needs work

Hmmm. As a note, I think that the issue could be fixed in a less comprehensive way that wouldn't require the API changes here. block_custom_block_delete() could just verify that 'module' == 'block' before it does anything.

http://api.drupal.org/api/drupal/modules--block--block.admin.inc/functio...

At this point, that's probably more reasonable?

sun’s picture

Version: 7.x-dev » 8.x-dev
Priority: Major » Normal
Issue tags: -API change +Needs backport to D7

The hook_menu() changes would be a huge performance regression in combination with Contextual module being enabled. We've been at this point before. For details, see #606640: Use proper menu router paths for the block module (but no argument loader functions) (and http://drupal.org/node/607244#comment-2179262)

Additionally, the change to block_admin_configure() looks like it would break pretty much every block-related contrib module that exists.

Let's do #27.

Lastly, the actual bug:

So basically if you went to the delete URL with a different block (by typing in the URL -- the block module doesn't put up a link except for its own blocks), block module would still try to delete one of its own blocks.

...doesn't sound major to me at all.

Albert Volkman’s picture

Status: Needs work » Needs review
FileSize
682 bytes

So, something as simple as this? Or should it raise some sort of error?

jhodgdon’s picture

That looks like the right check, but as noted in some of the other comments above, the check should go into a (probably new) access function in hook_menu() so that the page cannot even be accessed, rather than in the page generating function, right?

If for some reason it can't go there, then just returning nothing is not the right solution. At a minimum it should do a drupal_get_message() and a drupal_goto() because I think returning nothing would just show a blank page.

Albert Volkman’s picture

I'm not sure. We're not really defining access, we're checking for a missing argument. Right?

jhodgdon’s picture

Status: Needs review » Needs work

It's not a missing argument -- it's an incorrect use of the function.The menu router item is only intended to be used for blocks defined in the user interface -- the block module only puts up delete links for UI-created blocks, not for blocks coming from other modules. The only way the function would even be called from the UI is if someone types in an incorrect URL (or is tricked into visiting an incorrect URL).

So I think there are three possibilities of how to handle this wrong URL:

a) Do what node/[nonexistent node ID]/delete would do (and probably most similar things like taxonomy delete of a nonexistent term): have a 404 error. The way to get a 404 is to have the menu router item be something like block/%block_delete/%delete, and then have a block_delete_load() function that would return FALSE if it's not a block it can delete.

b) Have a 403 error. The way to do that is to have an access function that returns FALSE if it's not a block the user can delete or the user doesn't have permission to delete blocks.

c) Send you back to the block admin page with a message saying you can't delete this block. The way to do that is to put it in the function that this patch uses, but instead of just "return", do drupal_set_message and drupal_goto.

To me, any of these three seems like an acceptable solution... thoughts?

Albert Volkman’s picture

Status: Needs work » Needs review
Issue tags: +Needs tests
FileSize
1.03 KB

I think a is a good choice since its the pattern used elsewhere. I'm assuming we should also add a test?

jhodgdon’s picture

Status: Needs review » Needs work

You've actually chosen option (b), not option (a) from #32 -- this will give a 403 error (not authorized) if an incorrect URL is chosen, not a 404 error (not found). To implement option (a), you would need an auto-load function.

Anyway, I think it's probably OK for it to give a 404... This patch looks mostly OK but:
- Yes, we need a test. Someone needs to create one (it should verify that you get a 404 if you try to delete a standard block such as maybe the user login block?). Then make a patch with just the test and upload it (so we can verify that the test fails without the code change), then make a patch with the test and the code change and upload it (so we can verify that the test passes).
- The documentation of the new access callback needs to follow the standards:
http://drupal.org/node/1354#menu-callback
(after "Access callback:" start with a verb).
- Also in the documentation, I don't think the module is "calling" the path. I think I would use phrasing more like "the module in the path" and "only blocks added by the block module can be deleted" instead.

Albert Volkman’s picture

Status: Needs work » Needs review
FileSize
799 bytes

Here's the test.

Albert Volkman’s picture

Here's the test with the patch.

Status: Needs review » Needs work

The last submitted patch, block_arg_check-851628-36.patch, failed testing.

Albert Volkman’s picture

The failed test passes locally. Looks to be the same issue as-

http://drupal.org/node/1779638

jhodgdon’s picture

Status: Needs work » Needs review

#36: block_arg_check-851628-36.patch queued for re-testing.

jhodgdon’s picture

Status: Needs review » Needs work

You're definitely on the right track now! But this is a bit weird:

// in the hook_menu():
-  $items['admin/structure/block/manage/%/%/delete'] = array(
+  $items['admin/structure/block/manage/%block_delete/%/delete'] = array(
     'title' => 'Delete block',
     'page callback' => 'drupal_get_form',
     'page arguments' => array('block_custom_block_delete', 4, 5),
// ...

/**
+ * Checks that value of argument equals 'block'.
+ *
+ * @param string $module
+ *   Module argument from URL path.
+ *
+ * @return bool
+ *   Returns TRUE if argument equals 'block'.
+ */
+function block_delete_load($module) {
+  return $module == 'block';
+}

Normally the magic load functions should load something and return it, and then return FALSE if the load doesn't work. The reason is that the output of the load function replaces the URL path segment. So with this load function, if the URL is admin/structure/block/manage/block/5/delete, then block_custom_block_delete() form function is going to get arguments ($module = TRUE, $id = 5). Before it got ('block', 5). Although the function is not checking the argument (which was the whole reason for this issue in the first place -- it probably should be? That still needs to be added to the patch in case the form function is called directly, right?), it shouldn't be passed TRUE for $module.

So I think this load function should be instead:

if ($module != 'block') {
  return FALSE;
}
return $module;

right?

Albert Volkman’s picture

Assigned: glipay » Unassigned
Status: Needs work » Needs review
Issue tags: -Needs tests
FileSize
1.69 KB

You're correct. Here's a revised patch.

jhodgdon’s picture

The tests passed, but... this doesn't look right to me:

-  $items['admin/structure/block/manage/%block/delete'] = array(
+  $items['admin/structure/block/manage/%block_delete/%/delete'] = array(

The previous version has manage/%x/delete and this new one has manage/%x/%/delete, which is a different path entirely. That can't be right? Something weird is going on...

Albert Volkman’s picture

Ah, in this commit the menu removed a parameter. Updated patch.

jhodgdon’s picture

Can you also attach the test as a separate patch so we can verify that the test fails without the patch? That is the best way I know of to assess test validity. Thanks!

Albert Volkman’s picture

No problem.

Status: Needs review » Needs work

The last submitted patch, block_arg_check-851628-test_only-45.patch, failed testing.

jhodgdon’s picture

Um. Is that the right URL in the test, given comment #42/#43?

Albert Volkman’s picture

Not it's not. Further, it appears that the whole process of upcasting has changed with D8. I'm not sure of the process, but doing the access check the way we are currently won't work.

Albert Volkman’s picture

So, I went this route, but realized that an AccessCheck would only get me a 403. Would the correct process be to check access in the delete method of the Block entity?

jhodgdon’s picture

So is this issue still a problem in Drupal 8?

In Drupal 7, you could formulate a URL that would look like it was for deleting a non-block-module block, and it would result in deleting a block-module block. Maybe in Drupal 8 you can't do that any more, because the URLs have changed, and we should just move this issue to Drupal 7 and fix it there?

Albert Volkman’s picture

Version: 8.x-dev » 7.x-dev

You're right. With the new path the original issue is no longer an issue in D8.