I am seeing a weird bug where occasionally after logging in as user "foo" the username in a logged in block shows "bar" i.e. the $user object has somehow switched from foo to bar.
I've checked all custom modules and the theme to make sure there is no voodoo going on when I use global $user; but no leads.
Also placed additional watchdog messages inside user_authenticate and user_authenticate_finalize but the user object seems to be behaving.
How else can I track this error down?
A user switch can happen with very simple code like this :
global $user;
$some_user = user_load('uid' => SOME_UID);
$user = $some_user;
Perhaps you can try looking at assignments like the last one or uses of global $user
Related
When a user logs into a Meteor application a session is created. How long does it take for the session to expire after the user has closed the browser?
Does the session expire even if the browser is not closed?
Is it possible to react to the closing of a session? By invoking a callback for example.
I was looking for stale session / session timeout functionality for a meteorjs app and ran across this answer when looking for a suitable package to use.
Unfortunately the meteor-user-status package mentioned by Andrew doesn't seem to do a timeout.
I continued to look, found a couple of other packages, but couldn't get them to work for me - so I wrote a very small and simple package inspired by the others to do exactly what the questioner is asking for here i.e. force a user log out after a defined period of inactivity (whether the browser is open or not).
It does not, however, provide a callback (as it's the server that forces the logout) but this could probably be done with a Dep.autorun looking at Meteor.userId().
You can try it by typing
mrt add stale-session
And find details of how it works and how it can be configured here:
https://atmosphere.meteor.com/package/stale-session
and the code is open sourced here:
https://github.com/lindleycb/meteor-stale-session
Use the package I created that tracks user status, both overall and in several different browser sessions:
https://github.com/mizzao/meteor-user-status
With this, you can react to both sessions being closed and users logging out (see README). I've implemented it only for logged-in users, but you can do something similar if you want to track anonymous users.
I've been using zuuk:stale-session and I too initially wished it had a callback, but I solved it with an elegant solution (IMHO).
My app has a login template that get's rendered when if (! Meteor.user()) is true. It used to just run this.render('login') template which sufficed, but it still left the logged-in menu structure available. So, I switched to to Router.go('login') which has it's own layoutTemplate. So now when inactivity triggers the stale-session to delete the tokens for the user, the page goes to /login rather than just rendering the login template within whatever route was left stale.
Here's my code in router.js:
/** [requireLogin - make sure pay area is walled off with registration] */
var requireLogin = function() {
if (! Meteor.user()) {
// If user is not logged in render landingpage
//this.render('login');
Router.go('login');
this.next();
} else {
//if user is logged in render whatever route was requested
this.next();
}
}
/**
* Before any routing run the requireLogin function.
* Except in the case of "landingpage".
* Note that you can add more pages in the exceptions if you want. (e.g. About, Faq, contact...)
*/
Router.onBeforeAction(requireLogin, {
except:['terms','privacy','about','features','home']
});
I would like to limit the use of some url's. Let's say node/add and node/7 (just random examples). I'm thinking the best way to do this is to use the user_access function.
But as we are used to it, the Drupal documentation doesn't help much. When I just use the function, I get the message the function is already in use. So my best guess is to use this existing function with my own arguments in my custom function in my custom module.
But in this way I need to catch the page before loading it. Or I'm I missing something here?
EDIT:
I've set this
global $user;
$items['node/add/%']['access callback'] = array('_mymodule_node_access');
$items['node/add/%']['access arguments'] = array(0,2, $user);
But for some reason, Drupal isn't picking up the % card for all types. It's just working for one type (script). Other terms like page or fiche aren't getting picked up... % is a Drupal wildcard right?
EDIT:
I just found out there are already some paths in the database. How can I overwrite them? What I need is one selector which can select all four content types (fiche, page, script and news-item).
The way to define a particular access function for a path is to set the access callback for the path's menu item in hook_menu(). This is slightly different for existing paths, in that you need to implement hook_menu_alter() to edit the existing access callback for that path:
function mymodule_menu_alter(&$items) {
$items['node/add']['access callback'] = 'mymodule_node_add_access_callback';
}
function mymodule_node_add_access_callback() {
// return TRUE to allow access, FALSE to deny
}
This gets a bit more fun when we're talking about node pages as their menu items is defined using a wildcard node/%. This means that using hook_menu_alter() you can only change the access callback for all nodes.
Fortunately Drupal has a hook_node_access hook to come to the rescue:
function mymodule_node_access($node, $op, $account) {
$restricted_nids = array(7, 10, 12);
if (in_array($node->nid, $restricted_nids) && $op == 'view') {
if ($some_condition_is_true) {
return NODE_ACCESS_ALLOW;
}
return NODE_ACCESS_DENY;
}
return NODE_ACCESS_IGNORE;
}
Hope that helps
EDIT
If that all seems like a bit much hassle you might get some joy installing the Path Access module, I think it has the functionality you're after.
ANOTHER EDIT
I think the reason overriding the wildcard isn't working in this case is because the node module explicitly defines a path for each node type, e.g. node/add/page, node/add/article, etc. Because Drupal will take an exact match (node/add/page) over a wildcard match (node/add/%) you're actually overriding the wrong menu item.
Try specifying the path explicitly in your hook_menu_alter() function (note that the access callback should be a string and not an array as you currently have):
$items['node/add/page']['access callback'] = '_mymodule_node_access';
$items['node/add/page']['access arguments'] = array(0,2, $user);
It's also worth noting that the $user object you're passing will always be the user object of the logged in user who cleared Drupal's caches (since menu items are rebuilt when the cache is rebuilt). If you're looking to pass the current logged in user (i.e. the one logged in at the time the page is accessed) that's a different thing altogether...I'd advise asking another question on it as it can be a tricky bugger and you want to get as much input as possible from people on here.
This snippet of code responds for user#1 but not for other logged in or anonymous users:
function module_menu_alter(&$items) {
$items["node/add/page"]['access callback'] = 'module_access_callback';
}
function module_access_callback(){
die('responding here - test');
}
What am I doing wrong?
If that is the code you are really using, then the access callback function is wrong, as it should return TRUE when the currently logged-in user has access to the menu, and FALSE when the user doesn't have access to the menu. It doesn't use die().
This is what reported in the documentation for hook_menu():
"access callback": A function returning TRUE if the user has access rights to this menu item, and FALSE if not. It can also be a boolean constant instead of a function, and you can also use numeric values (will be cast to boolean). Defaults to user_access() unless a value is inherited from the parent menu item; only MENU_DEFAULT_LOCAL_TASK items can inherit access callbacks. To use the user_access() default callback, you must specify the permission to check as 'access arguments'.
If you are not showing the code you are using, then what follows can explain the behavior you are seeing.
The first user, or user #1, is a particular user because user_access() always return TRUE for that user.
This is evident in the code of that function, which explicitly check the user ID is equal to 1.
// User #1 has all privileges:
if ($account->uid == 1) {
return TRUE;
}
If the access callback returns FALSE for authenthicated users, and it is using user_access(), it is because the authenticated users don't have the permission passed to the function.
It could also be you are checking for more than one permission, and you are using user_access('first permission') && user_access('second permission'), instead of user_access('first permission') || user_access('second permission') (or vice versa). For the user #1 that doesn't make any difference, as the result of user_access('first permission') && user_access('second permission') and user_access('first permission') || user_access('second permission') is always TRUE, even if you pass to the function a string for a permission that is not defined from any module.
OK It turns out the answer is actually very simple...
You're calling menu_get_object() to retrieve the node, but no node exists on node/add/page. In fact quite the opposite; it wouldn't make sense to have a node available on the page to add a node, as it hasn't been created yet! The 403 is a side effect of this.
You'll need to make your access decision based on some other value (normally the logged in user along with a permission as #kiamlaluno has done a very good job of explaining in his answer).
Also do make sure you return TRUE or FALSE from your access callback as #kiamlaluno has also stated :)
EDIT
Just to say that this is partially an answer to https://stackoverflow.com/questions/8342169/drupal-hook-menu-alter-menu-get-object-error, which explains why there are functions mentioned in this answer that aren't mentioned in the question.
/**
* Implementation of hook_menu_alter().
*/
function joke_menu_alter(&$callbacks) {
// If the user does not have 'administer nodes' permission,
// disable the joke menu item by setting its access callback to FALSE.
if (!user_access('administer nodes')) {
$callbacks['node/add/joke']['access callback'] = FALSE;
// Must unset access arguments or Drupal will use user_access()
// as a default access callback.
unset($callbacks['node/add/joke']['access arguments']);
}
}
The above function is from the pro development drupal. I can't understand it well. Why must I unset the access arguments (unset($callbacks['node/add/joke']['access arguments']);)?
Thank you.
That entire example seems broken and bad. In short, a joke. First, let me answer your question, then I'll go on to explain why you shouldn't follow that example in practice.
From includes/menu.inc:
if (!isset($item['access callback']) && isset($item['access arguments'])) {
// Default callback.
$item['access callback'] = 'user_access';
}
Unsetting the access callbacks when you no longer need them (relying on a boolean now, after all) prevents the over-clever logic in Drupal's routing system from slapping in user_access() just so it has something to do.
Now, on to why that's bad code.
hook_menu() and hook_menu_alter() are both run on cache clear (more specifically when the menu routing system is rebuilt). This means that the permissions of whichever user hits the site to rebuild the menus will be hard-coded into menu routing behaviors. This is a very bad and inconsistent arrangement.
If you want to block access to a path based on a permission, you need to change the callback to something that will test for that permission. Then when the menu is rebuilt, it will check the new callback function per page load to see if the current user should be granted permission.
A simple example of this might look like:
/**
* Implementation of hook_menu_alter().
*/
function joke_menu_alter(&$items) {
$items['node/add/joke']['access callback'] = 'user_access';
$items['node/add/joke']['access arguments'] = array('administer nodes');
}
Now we have a function which takes the node/add/joke path and declares that the only thing that matters is whether or not the user has administer nodes permission. Of course, that's a little more limited than the apparent intentions of the example, which were to preserve the existing access controls, but also require the user to have administer nodes permission.
That is also fixable, but is more complicated. To borrow some concepts from the Spaces project:
/**
* Implementation of hook_menu_alter().
*/
function joke_menu_alter(&$items) {
$path = 'node/add/joke';
$items[$path]['access arguments'][] = $items[$path]['access callback'];
$items[$path]['access callback'] = 'joke_menu_access';
}
function joke_menu_access() {
$args = func_get_args();
$access_callback = array_pop($args);
$original_access = call_user_func_array($access_callback, $args);
return $original_access && user_access('administer nodes');
}
We have successfully wrapped the original access callback in a new access callback, to which we can add whatever additional logic we need.
Note that in the last two function examples, I used the $path variable to keep the code simple. I also separated $original_access to it's own line and had it checked first, in practice I would check user_access() first as it would almost certainly be more performant than whatever happens in the original access callback.
The comment directly above that line explains it?
access callback is the function that is called (or TRUE/FALSE) and arguments is what is passed to that function. You are setting the callback to false and therefore always deny access to that router item.
And now, as the comment is saying, you also needto unset the arguments or Drupal will still use user_access() (The default access callback).
I want to be able to upgrade user's permission after the order status shows complete.
I figured out that I should use hook_order hook in order to achieve that. But how do I get to know which user has created that order and how do go about updating the permissions as well as setting up the expire time for that role automatically.
I want this hook to be called as soon as the payment is made and the order is completed.
Any pointers will be valuable.
In the hook_order, 3 parameters are passed. Third parameter depends on the first one. When the first parameter is 'update', third parameter is the status to which the order is going.
hook_order($op, &$order, $arg2){
switch($op){
case 'update':
if($arg2 === 'completed'){
// This order got marked completed
}
}
}
$order->uid will give you the user that created the order. You can do something like the following
$user = user_load(array('uid' => $order->uid));
// update the roles assigned to user
user_save($user);
For expiring the role, you will need to write a module that will keep track of the duration and will do something like above when the time expires. Or you can use role_expire module and see if that helps.