Reproduce step:

- Visit user/reset/1/1/1 as an anonymous user

Possible solutions?
- Change all incorrect instances of drupal_access_denied() to return MENU_ACCESS_DENIED (should probably be done anyway)
- Since drupal_access_denied() is actually for instances when the above is not possible, we need to fix the function too. Maybe don't let drupal_deliver_page() be executed twice? Or call drupal_exit() inside that function as we do for drupal_goto()

Comments

criticalpatch’s picture

StatusFileSize
new568 bytes
new136 KB

I can confirm this bug exists in drupal 7 as well as the current stable release 6.17.

To clarify the steps to reproduce this error, you must visit a uid that does not exist!
Since I believe uid 1 will always exist, the url mentioned above will not display the error.
Try visiting user/reset/0/1/1 instead.

I've attached a screenshot taken in a fresh install of 6.17 (The page looks similar in Drupal 7, of course).

I manually checked all the places where drupal_access_denied() is called in core, and I have confirmed that this is the only affected page. I've attached a patch... it simply adds a drupal_exit() after the offending function call.

This issue is also reported in the drupal 6.x queue here:
#653404: Use of drupal_access_denied inside drupal_get_form is not correct

berdir’s picture

Status: Active » Needs work

You can replace the call in this function with "return MENU_ACCESS_DENIED;", that should work too without having to call drupal_exit(). If this is the only place in core where it is used wrong, then I guess we don't need to add the drupal_exit().

criticalpatch’s picture

Status: Needs work » Needs review

When I replace the call with "return MENU_ACCESS_DENIED", I get the error:
Unsupported operand types in /var/shared/sites/drupal7/site/includes/form.inc on line 855.

I think this is because the page callback is drupal_get_form(), which calls user_pass_reset() indirectly. Other places in core use drupal_access_denied() followed by drupal_exit() to avoid this problem, as per the comments in common.inc:710:

 /**
 * Page callback functions wanting to report an "access denied" message should
 * return MENU_ACCESS_DENIED instead of calling drupal_access_denied(). However,
 * functions that are invoked in contexts where that return value might not
 * bubble up to menu_execute_active_handler() should call drupal_access_denied().
 */

Status: Needs review » Needs work

The last submitted patch, add-drupal-exit-after-access-denied.patch, failed testing.

criticalpatch’s picture

Status: Needs work » Needs review
StatusFileSize
new553 bytes

Whoops, I made a small mistake generating the patch. Lets see if this does it.

Status: Needs review » Needs work

The last submitted patch, add-drupal-exit-after-access-denied.patch, failed testing.

criticalpatch’s picture

StatusFileSize
new592 bytes

Okay, now it should really work...I was making my patch from the module directory instead of the root.

criticalpatch’s picture

Status: Needs work » Needs review

Queued for testing.

moshe weitzman’s picture

Status: Needs review » Needs work

er, if we are denying access we need to generate a 403 header and say so. this patch does neither, AFAICT

berdir’s picture

Status: Needs work » Needs review

That is already done. All we do here is avoid to print it *twice*. The existing drupal_access_denied() call calls drupal_deliver_page(MENU_ACCESS_DENIED) and that calls http://api.drupal.org/api/function/drupal_deliver_html_page/7

moshe weitzman’s picture

Status: Needs review » Reviewed & tested by the community

i see. thx.

damien tournoud’s picture

Status: Reviewed & tested by the community » Needs work

The fix should be ok for Drupal 6, but for D7, could we please fix that properly?

A form callback should not contain anything that has a side effect. The form part of this function (which is buried down below a pile of action logic) should be moved to a separate callback. The main callback should use return MENU_ACCESS_DENIED, problem solved.

criticalpatch’s picture

Status: Needs work » Needs review
StatusFileSize
new3.94 KB

@Damien Tournoud: That is a good point. I think I have done what you wanted, could you please verify?

criticalpatch’s picture

*Shameless bump* Or anyone else for that matter. It should simple to follow.

nette_sk’s picture

damien tournoud’s picture

I'm very sorry for the delay. This felt completely out of my radar.

The patch looks pretty close to me. Two small concerns:

 /**
+ * Form builder; Allow the user to log in and change their password
+ * @ingroup forms
+ * @see user_pass_reset()
+ */

There should be a new line after the function summary.

+function user_pass_reset_form($form, $form_state, $uid, $timestamp, $hashed_pass, $action = NULL) {
+  $user = reset(user_load_multiple(array($uid), array('status' => '1')));

You can pass the fully loaded $user object from the menu callback instead of reloading it.

moshe weitzman’s picture

Status: Needs review » Needs work
halmsx’s picture

Version: 7.x-dev » 6.20

hi

i was creating a custom page using the same function and got the same double page display.

but..

function foo () {
...
   return drupal_access_denied (); <= do this works ok
}

function foo () {
...
   drupal_access_denied (); <= do this, gives double page display
}
berdir’s picture

Version: 6.20 » 7.x-dev

Please don't change the version. This needs to be changed in 7.x first and it is not a bug in drupal_access_denied() that is the way that function works and why it's much better to use "return MENU_ACCESS_DENIED" in 7.x

damien tournoud’s picture

@halmsx: Please do not change the version information.

This issue is to solve a bug in a specific form of Drupal core, that does not use the API properly.

If you want to return an Access denied from your menu callback, in Drupal 6, use either:

drupal_access_denied();
exit;

Or (preferably, because compatible with Drupal 7):

return MENU_ACCESS_DENIED;

None of the code examples you provided are actually correct.

halmsx’s picture

apology. my bad. didnt noticed the version.

charlie-s’s picture

Damien, thanks for your note. I like how simple return MENU_ACCESS_DENIED; works, but I don't understand why drupal_access_denied() function even exists if it doesn't handle the return itself. Can you explain?

Johnny vd Laar’s picture

I am still experiencing this issue in drupal 7.16.

andeersg’s picture

Issue summary: View changes
Status: Needs work » Closed (fixed)

I think this is not a problem anymore. This commit: http://cgit.drupalcode.org/drupal/commit/modules/user/user.pages.inc?h=7..., solves it by calling "drupal_exit() after access denied.

drupal_access_denied();
drupal_exit();

.