Is it possible to set plugins permissions on a per Category basis?
Ref: Vanilla 2.0.8.14
I'm using a couple of plugins that register some permissions (e.g. Plugins.MyPlugin.CanDoSomething), and I wanted to enable/disable some of them on some Categories. However, when I click on "This category has custom permissions", I only see the system ones, i.e. the ones for Comments and Discussions. No plugin-specific permission is listed.
After some debugging, I noticed that the permissions are registered and retrieved correctly when the Category page is rendered, but then they are filtered out in PermissionModel::GetJunctionPermissions()
. The line that filters them out is the following:
foreach($Row as $PermissionName => $Value) { if(!($Value & 2)) continue; // permission not applicable to this junction table //[...]
I don't know what "2" means, so I went on searching, to see if I could find a clue. All I could find, apparently related to the magic number, was PermissionModel::Define()
:
foreach($PermissionNames as $Key => $Value) { if(is_numeric($Key)) { $PermissionName = $Value; $DefaultPermissions[$PermissionName] = 2; } else { $PermissionName = $Key; if ($Value === 0) $DefaultPermissions[$PermissionName] = 2; elseif ($Value === 1) $DefaultPermissions[$PermissionName] = 3; elseif (!$Structure->ColumnExists($Value) && in_array($Value, $PermissionNames)) $DefaultPermissions[$PermissionName] = $PermissionNames[$Value] ? 3 : 2; else $DefaultPermissions[$PermissionName] = "`{$Value}`"; // default to another field } // [...]
I don't know what these magic numbers 0, 1, 2 and 3 mean, nor I could figure out why they are assigned to some permissions, therefore I gave up searching (a grep for "2" gives way too many results). My question, though, still stands: is it possible to tell Vanilla that I would like some plugin permissions to be enabled/disabled for each Category?
Thanks in advance for the help.
Answers
While I can't tell you for certain if you can achieve this through the framework code, it looks like the plugin permissions can be set per category. I am just inferring this from the mysql db permissions table.
As far as the magic number, it looks like they are using numerals and bitmasking for the actual permissions. Think of something like file permissions using chmod 700 that means the user can read (1) + write (2) + execute (3) = 7. This lets you check an individual permission by just bitwise ANDing it (if(!($Value & 2))). Keep in mind this is mostly speculation and inferences.
Search first
Check out the Documentation! We are always looking for new content and pull requests.
Click on insightful, awesome, and funny reactions to thank community volunteers for their valuable posts.
I also thought that by looking at the table, apparently there is no real difference between the system permissions and the plugin ones, apart that the former get set to "2" and, therefore, picked up by the PluginModel.
That would explain their usage. Now it would be interesting to find out what these bit masks mean. It seems I will have to go deeper, "down into the mines of Moria".
My shop | About Me
if($this->CheckPermission(Plugins.MyPlugin.variable, FALSE, 'Category', $JunctionID)) {
The JunctionID associated with $Permission (ie. A discussion category identifier).
use category or create your own junction table.
permission 3 never gets filtered out.
I may not provide the completed solution you might desire, but I do try to provide honest suggestions to help you solve your issue.
Thanks for the reply. This how I'm checking it now, but the issue is that the permission cannot be set on a per-category basis. That is, it's global, "all categories or none". If you open a Category and click on "this category has custom permissions", you will see that none of the Plugins permissions appear in the page. In my case, the permissions must not be global, they change from category to category.
Let's make a simple example: I want to prevent users from posting a Discussion in Category A, but they can post a Question. Vice-versa, they can post a Discussion in Category B, but not a question. A new plugin could register the following permissions:
Then, the plugin will check, before allowing to post:
The issue is that
CanPostDiscussion
andCanPostQuestion
will not appear in Category permissions, only in the Global ones. Therefore, before the User posts something, the check will always pass, or always fail, depending on the global setting and regardless of the Category.My shop | About Me
if all else fails.
create a config setting for allowed categories in an array via category id.
$CategoryId = GetValue('CategoryID',$Sender->Discussion);
multiples
if ( ((in_array($CaegoryID, $AllowedCategoryArray)) && (($this->CheckPermission(Plugins.MyPlugin.CanPostQuestion))) {
add a settable multiple category setting in dashboard that writes to a config statement.
I may not provide the completed solution you might desire, but I do try to provide honest suggestions to help you solve your issue.
That would be a partial workaround, but I forgot to mention something important (stupid me): the settings have to be per Category, and per Role. Moderators may post Questions and Discussions in Category A, Users can only post Questions in Category A, Administrators can post anything.
It's fairly complex, that's why I hoped I could to leverage the existing Permissions model, which already implements role permissions checks.
My shop | About Me
yea.
I asked about permissions here when i first joined the forum ...
http://vanillaforums.org/discussion/19084/could-any-developers-shed-some-light-on-checkpermissions#latest
not many responses to that question
Unless you get a better answer - a combo of custom categories to control posting and checking category for plugin use. 3 conditionals per.
I may not provide the completed solution you might desire, but I do try to provide honest suggestions to help you solve your issue.
Permissions are agnostic, they have no awareness of the plugin other then what you put in your logic.
You can't do it through the plugin info RegiterPermisions becuase it can't supply all the parameter currently. If you do it explicitly it probably a lot clearer anyway. You could try:
You could do this this in Setup.
The relevant section is the bit with
JunctionTable
. I believe that 1 gives you a default of on.There are some gems of information in vanilla/dashboard's settings/structure.php. I recommend that you have a look.
grep is your friend.
Ah, finally!
PermissionModel::Define()
, that was the method I was looking for! I went through thePermissionModel
, but I must have missed it. That's an excellent starting point, and I will have a look atstructure.php
as well.My shop | About Me
how can you have missed it? Look at your previous posts, you referenced it lol
grep is your friend.
That's true! I can tell you how I missed it. It was a combination of the following:
The important thing is that I now have a lead, thanks to your suggestion. Besides, I learned that structure.php also contains valuable information. At the end, despite the silliness, the outcome is positive.
My shop | About Me
Update: I used
PermissionModel::Define()
to define the new permissions, specifying Category as the junction table as PermissionCategoryID as the junction column. Apart from some values changed in Gdn_Permissions table, which I speculate will come into play during a call toCheckPermission()
, nothing else has changed.Next step will be finding out how to display those permissions in the Category Permissions page (there's little point in having per-category permissions, if they cannot be changed without opening the table).
It will work, eventually.
My shop | About Me
I think I found out what's happening, and I think it's due to a bug. Here's the (long) story.
When a new Permission is registered by a plugin, its name is, usually, Plugins.SomePlugin.SomePermission. By using the following code, thanks to the suggestion given by @x00, I was able make them pass the first test (i.e.
if(!($Value & 2))
):Gdn::PermissionModel()->Define(array(
'Plugins.MyPlugin.CanPostQuestion',
'Plugins.MyPlugin.CanPostDiscussion'),
'tinyint',
'Category',
'PermissionCategoryID');
This, however, is not enough. The RegisterPermissions section of the plugin declaration still has to be present (the reason for this is below). So, I also added that:
Despite the permissions passing the above test, they were still not appearing in the Categories Permission page. I checked the code again, and I found what I think is a bug. The statement that excluded them is in
PermissionModel::GetJunctionPermissions()
:The
$Namespaces
array contained the following (amongst others):[...]
The Plugins.xyz namespaces are added automatically, by PermissionModel::GetAllowedPermissionNamespaces(), which parses the RegisterPermissions attribute in plugin's declaration (that's why it still has to exist, even if
Gdn::PermissionModel()->Define()
is called explicitly). The namespace is extracted using astrrpos()
, which cuts away everything until the rightmost dot.The issue is that the check extracts the permission namespace using
strpos()
and stopping at the leftmost dot. That is, the value comes out as just Plugins, which doesn't exist in the list, thus excluding the permission as "not in allowed namespaces".I then started wondering why the discrepancy in the logic and, most importantly, why the permissions show correctly in the global page. I debugged that page, and there I found the answer. Method
PermissionModel::GetGlobalPermissions()
performs almost the same check, but with an important difference:That is, it checks it twice:
I then modified
PermissionModel::GetJunctionPermissions()
in Core, and, finally, the Plugins Permissions show in both the Category Permissions and the Global Permissions pages. They also seem to be processed correctly byGdn_Session::CheckPermission()
.Solutions
There are two solutions for the issue (actually, one is a workaround):
1. Solution: fix the check in
PermissionModel::GetJunctionPermissions()
by adding the second clause, as described above. This is the solution I adopted at the moment.2. Workaround: use a single dot in the permission name (e.g. declare it as just "MyPlugin.SomePermission"). This is not standard, but it will trick the PermissionModel into extracting the same namespace, since there is only one dot. One of the side effects of this workaround is that the permissions will be rendered in their own separate table in the Permission pages.
The same discrepancy exists in 2.0.x, 2.1a and 2.1b. I'm now wondering if I should report this as a bug, or if the original namespace check was like that by design.
My shop | About Me
This definitely smells of a bug. Please file it on github.
Also, thanks for the insight!
Search first
Check out the Documentation! We are always looking for new content and pull requests.
Click on insightful, awesome, and funny reactions to thank community volunteers for their valuable posts.
Filed as issue 1541.
My shop | About Me