hook_menu_alter() inconsistent response - drupal

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.

Related

Firestore security rules based on map values

I want to store if a user is permitted to read a document in the document itself, based on the user's email address. Multiple users should have access to the same document.
According to the documentation Firestore does not allow querying array members. That'S why I'm storing the users email addresses in a String-Bool Map with the email address as a key.
For the following example I'm not using emails as map keys, because it already doesn't work with basic strings.
The database structure looks like that:
lists
list_1
id: String
name: String
owner: E-Mail
type: String
shared:
test: true
All security rules are listed here:
service cloud.firestore {
match /databases/{database}/documents {
match /lists/{listId=**} {
allow read: if resource.data.shared.test == true
}
}
}
Edit: It also doesn't work if I use match /lists/{listId} instead of match /lists/{listId=**}
How I understand it, this security rules should allow reading access to everyone if the value in the map shared[test] is true.
For completness sake: This is the query I'm using (Kotlin on Android):
collection.whereEqualTo("shared.test", true).get()
.addOnCompleteListener(activity, { task ->
if (task.isSuccessful) {
Log.i("FIRESTORE", "Query was successful")
} else {
Log.e("FIRESTORE", "Failed to query existing from Firestore. Error ${task.exception}")
}
})
I'm guessing that I cannot access map values from the security rules. So what would be an alternative solution to my problem?
In the Firestore rules reference it's written that maps can be accessed like that resource.data.property == 'property' so, what am I doing wrong?
Edit: This issue should be fixed now. If you're still seeing it (and are sure it's a bug with the rules evaluator), let me know in the comments.
I've chatted with some folks here about the problem you're encountering, and it appears to be an issue with the security rules itself. Essentially, the problem seems to be specific to evaluating nested fields in queries, like what you're doing.
So, basically, what you're doing should work fine, and you'll need to wait for an update from the Firestore team to make this query work. I'll try to remember to update this answer when that happens. Sorry 'bout that!
Whenever you have (optional) nested properties you should make sure the property exists before continuing to check its' value eg.
allow read: if role in request.auth.token && request.auth.token[role] == true
in your case:
allow read: if test in resource.data.shared && resource.data.shared.test == true
, I was struggling a long time with roles until I realized that on non-admin users the admin field is undefined and firestore rules just crashes and doesn't continue checking other possible matches.
For a user without token.admin, this will always crash no matter if you have other matches that are true eg:
function userHasRole(role) {
return isSignedIn() && request.auth.token[role] == true
}

Check if username exists in Meteor

Been digging around for a solution but none for Meteor. If any, please let me know. I want to check if a username is already taken.
I understand that this only works on the server side only:
u = Accounts.findUserByUsername('foo');
console.log(u.username); #=> foo
I cant get my head around their pub/sub as I can only see information based on the current user. Is meteor saying that what I want is not possible?
When a user is filling out their details upon registration, I want them to be alerted (as they type) if the username they are using is already taken. But that logic I can easily code but need to know how to talk to the server to tell me the information.
You could write a Meteor method for that:
Meteor.methods({
doesUserExist(name) {
return Accounts.findUserByUsername(name) != null;
}
});
Note that you have to define this method on the server but not on the client (e.g., by defining it in a file inside the server directory). That way Meteor won't try to simulate it on the client (which would fail because Accounts.findUserByUsername is not defined there).
Call the method as the user types:
Meteor.call('doesUserExist', name, function(error, result) {
// `result` is true if the user exists.
});

Find out all the Queries listening on a Meteor.js Collection

In Meteor 0.7.0.1, is it possible to count/find out the all the queries that are currently listening to a particular Collection?
I am trying to create a function which does: Whenever the number of users listening on a particular query (eg: myCollection.find({color:'red'}) becomes non-zero, execute a function whenever documents are changed/added to a second Collection anotherCollection.
When/how is the find method called? If it's called when someone hits a button on the page, for instance, simply increase a serverside variable that will increase when that happens. To decrease this variable when the user leaves the page, listen to the window.onbeforeunload event and decrease the count when it happens.
Alternately, if you have a login system, assign a boolean value such as online to each user. When they log in, make their online status true with the following code. if(Meteor.user()){Meteor.user().online=true;}. Make sure an onbeforeunload sets their online status to false when they leave. Then, do something like Meteor.users.find({online:true}).size() to get the amount of users online.
Basically, rather than update when myCollection.find({color:'red'}) is called, put that in a function. For instance:
if(Meteor.isClient){
Session.set('browsing', false);
function findColor(c){//Alerts the server when the user attempts to get a color.
//This is presuming they don't use the plain MongoDB command.
//Your button/however you're finding the color should use this command
if(!Session.get('browsing'))//if it's already true, don't increase the count
{
Meteor.call('incBrowsing');
Session.set('browsing', true);
}
return myCollection.find({color:c});
}
window.onbeforeunload = function(){Meteor.call('decBrowsing');};
}
if(Meteor.isServer){
var browsing = 0;
Meteor.methods({
incBrowsing: function(){browsing++;},
decBrowsing: function(){browsing++;}
});
}
I haven't tested this, but I hope it works for you. You didn't provide too many details on your problem, so feel free to comment below if you or I need to clarify something.

How to properly use user_access($string, $account) in Drupal?

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.

How to use hook_menu_alter() to manipulate path access control

/**
* 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).

Resources