If you have 'administer users' permission then you can access a "contact form" for user 0 on any Drupal site at user/0/contact.
In this code
function _contact_personal_tab_access($account) {
global $user;
if (!isset($account->contact)) {
$account->contact = FALSE;
}
return
$account && $user->uid &&
(
($user->uid != $account->uid && $account->contact) ||
user_access('administer users')
);
}
it should probably say
return
$account->uid && $user->uid &&
...
I imagine that the uid property was omitted in error; $account will always evaluate to TRUE in this context.
This fix will also stop the Views handler which generates the contact user link from showing the link for user 0 (if the current user has 'administer users' permission). See also #523222: Access check is done twice on link to a user's contact form, and incorrectly, which is where I tripped over this problem.
I've tested this tiny fix on 6.12. Let's see what testbot makes of it.
Comments
Comment #1
gpk commentedNow with patch.
Comment #2
zoen commentedThis patch totally works. Before patching, there's a contact form for the anonymous user at user/0/contact, which, if you actually submit it, delivers you to the "Access denied" page at user/0 with a message proudly (and falsely) stating that "Your message has been sent". After patching, user/0/contact gives you an "Access denied" page, no matter what your permissions are.
BUT. I have an issue with "Access denied" messages appearing where, really, we're not denied access to anything; there's just nothing there. I think it would be better if we could display a "Page not found" error at user/0 and user/0/contact instead. Thoughts?
Comment #4
gpk commented>really, we're not denied access to anything; there's just nothing there. I think it would be better if we could display a "Page not found" error at user/0 and user/0/contact instead. Thoughts?
IMO it's not worth trying to do this any better/differently. contact_menu() defines a callback for each user/%user/contact, where %user is a valid uid. So in a sense there really is a page defined at user/0/contact..! Perhaps there shouldnt be, but in that case there shouldn't really be a page at user/0 either. I think having access denied for both is good enough, and is at least consistent. Drupal (Views) doesn't automatically generate links to user/0, and with this fix, won't generate links to user/0/contact either. If people navigate there manually or create their own links to these URLs then that's their problem!
Comment #5
catchI think it's fine to issue a 403, but can we add a test to visit the path and assertResponse() that it's actually denied? Should be a two line change to contact.test
Comment #6
gpk commentedThanks catch, this patch adds the 2 lines and also a few more for another test, to test it more thoroughly.
Comment #7
gpk commentedIt also occurred to me when looking at the tests that the 'administer site-wide contact form' permission should be 'administer contact forms', since the settings tab contains settings for the personal contact forms. Grateful for any thoughts on whether the permission should be renamed (in which case presumably an update would be needed to preserve existing permissions granted to roles), or just fudge it using the permission's title and description http://api.drupal.org/api/function/contact_permission/7. (In a separate issue.)
[update] see #573510: Usability: contact module permissions need clarifying
Comment #8
damien tournoud commentedGiven that this is pretty common requirement, I suggest we add a %user_authenticated placeholder, and the corresponding loading function:
Comment #9
damien tournoud commentedComment #10
catchI like Damien's suggestion, could clean up a lot of things elsewhere.
Comment #11
gpk commentedOK chaps, just a thought about the name of the placeholder. In terms of user roles and permissions, an authenticated user is one whose account is active, i.e. they have not been blocked and if the "Require e-mail verification when a visitor creates an account" checkbox is checked then they will also have verified their email address.
In this context we are talking about user accounts which may not all be fully authenticated. Perhaps %user_registered would be clearer? Or even just %user_not_anonymous??!!?? Thoughts??
Comment #12
catchI like %user_registered
Comment #13
gpk commentedAh, I was just coming round to %user_not_anonymous, which is hard to misconstrue. Casting vote anyone ...?
Comment #14
zoen commentedI like %user_not_anonymous, too.
Comment #15
gpk commentedHere's a patch that just applies the new placeholder to the personal contact form tab, for tesbot's verdict.
Comment #16
dave reidPlease just check $account->uid instead of $account->uid > 0. Looking good.
Comment #17
gpk commentedThanks Dave, I would probably have said the same but Damien suggested $account->uid > 0 at #8. Not sure why!
Also he notes that "this is a pretty common requirement". I guess other places would also benefit from %user_not_anonymous. I assume they should go into a separate patch?
Have also added back 2 tests. The first assertion is not really anything to do with this issue any more but should be in there anyway and was in #15.
Comment #18
dave reidPlease re-use the $admin_user variable from earlier in the test because it already has the 'administer users' permission.
This review is powered by Dreditor.
Comment #19
gpk commentedThanks Dave, unless I'm mistaken (which is not unlikely!) the $admin_user from earlier in this test only has 'administer site-wide contact form' permission?
Comment #20
dave reidAh crap. That was an overlap from my own patch, so you were right. :)
But I'm a big fan of re-using what we have instead of creating new things. Let's change
$admin_user = $this->drupalCreateUser(array('administer site-wide contact form'));to
$admin_user = $this->drupalCreateUser(array('administer site-wide contact form', 'administer users'));Then we can just re-use that user.
Comment #21
gpk commentedYes I see what you mean, although on reflection the patch at #17 does confirm that 'administer users' permission elevates one's permissions such that one can always see other users' contact forms... Doing it as you suggest doesn't quite achieve this because it could have been the 'administer site-wide contact form' permission that gave this ability.
I wonder if that additional verification is worth the non-reuse?
Comment #22
dave reidRevsied patch with more documentation and way cleaner tests (meaning to do that for a while). gpk does this look good?
Comment #23
dave reidExecutive summary:
1. Adds a user_not_anonymous_load($uid) menu-load callback in user.module that will return FALSE if the anonymous user is loaded.
2. Cleans up some documentation in user.module.
3. Makes _contact_personal_tab_access() look a lot easier to read and understand, with inline comments to help!
4. Splits the existing personal contact tests into an access test, and a flood test. Adds a submitPersonalContact function and some private user objects in the test to help re-use code.
5. Adds test conditions for:
a. User trying to access their own contact form.
b. Admin user can still access contact forms that have been disabled by users
c. Users cannot contact the anonymous user (user/0/contact)
d. Make sure admins don't get locked out of personal contact forms when flood protection is triggered.
Comment #24
dave reidRe-rolled for recent changes.
Comment #25
dave reidAdding tag
Comment #26
sunGiven this menu access callback, I do not really understand why we need the new menu argument loader. The access callback seems to handle anonymous users properly already...?
Wrong indentation of entire PHPDoc block here.
Wrong indentation of closing parenthesis here.
@return docs do not map to the actual returned value.
s/was/is/
I'm on crack. Are you, too?
Comment #28
dave reidGood point. I guess the only advantage to having a non-anonymous-user loader would be that it gives a 404 on pages where it would be uid 0 instead of a 403, but not really a big deal either way. I can live without the loader. It probably needs to have more discussion about things like included blocked users, etc.
Rerolled patch without the new menu loader and fixed coding standards.
Comment #29
sun"to content them" ? :)
This actually also catches the case where the to be contacted user disabled his contact form, while the comment only speaks about a missing preference.
I agree that adding a check for blocked users would be good. But aside from that, I don't see any further options to delay this patch.
oh wow! I actually didn't know that one can just do user_load() to load the current user. Nice docs! :)
This review is powered by Dreditor.
Comment #30
dave reidWe have an existing issue for blocked users in #586664: Users should not be able to contact blocked users but that's another bug report that can wait since I want this access function cleaned up for some other issues in contact.module.
Comment #31
sunLooks good.
Comment #32
gpk commentedMinor improvements to comments:
administrators I think...
"may not" would be more accurate, especially in light of the confusion/debate over this!
If you haven't changed the contact preference then your account's $account->contact will contain the value of variable contact_default_status that was current when your account was created, which may well lead to your form being enabled.
I know what you mean, but I think "Otherwise the user..." or "In all other cases the user..." would be more accurate.
Comment #33
dave reidGood catches in the documentation. I guess it's pretty obvious that in the end of the function is reached, access is granted, so I just chunked out that comment and moved it to the function's @return doc.
Comment #34
gpk commented@26,28
That came from Damien's @8. I don't think it was ever concerned with 403 vs 404, but rather was intended to do away with the additional
!$account->uidcheck in _contact_personal_tab_access(). Core has similar checks elsewhere, and I guess the idea was to roll out this loader in other places too. I agree it doesn't really belong here.Comment #35
gpk commentedCrossposted.
Comment #36
gpk commentedAlso, I have a feeling that global $user is not necessarily the "fully loaded $user object" (certainly that used to be the case.. session handling only loaded as much of $user as was stored in the {users} table), in which case the behavior of user_uid_optional_load() is being changed...
Comment #37
dave reidI see how I messed up user_uid_optional_load(). /me face palms.
Comment #38
gpk commentedI think we could do with a ! in there somewhere.. ;)
Comment #39
dave reidThe quality of my patches has been decreasing as I've been trying to pump them out with upcoming slush freeze. I need to slow down a little bit. :(
Comment #40
dave reidAdding backport tag.
Comment #41
sunYou can prove me wrong, but I really think that there are no issues left now ;)
Comment #42
dries commentedLooks great. Committed to CVS HEAD. Thanks.
Comment #43
dave reidLove to re-roll this for D6 since it doesn't change APIs. Thanks very much Dries!
Comment #44
andypostThis is a bug (user/0/contact) and should be fixed in d6 too.
Comment #45
dave reidBackport D6 patch attached including the documentation improvements to user.module as well. Does not change any APIs so should be good.
Comment #46
andypostApplies cleanly, so RTBC as straight backport
Comment #47
gpk commented@29:
>oh wow! I actually didn't know that one can just do user_load() to load the current user. Nice docs! :)
I think you actually need user_uid_optional_load().
Comment #48
gábor hojtsyThanks, committed this to Drupal 6. I noticed that the code inside user_uid_optional_load() clearly treated the uid optional (just like the function was named), but $arg was not technically optional. Good catch.
Also, fixed "a either a" to "either a" in the code docs for this function before committing. That minimal fix might be useful to bring in D7 as well.
Comment #49
gpk commentedThis also afflicts 5.x, so changing tag/version accordingly.
Comment #50
andypostAs pointed in #48 there'sa small typo, so let's back to 7 and return to 5
Comment #51
webchickCommitted #50. Now back to 5.x.
Comment #52
dave reid7.0 is out tomorrow, 5.x is obsolete, won't fix, sorry.