implode strings separated by commas? Ugh. We can do better.

Performance? I presume that most pages have very few user_access calls, the menu is cached and only the local tabs need them. I also think that your DB can do much better indexing on just a string than performing a LIKE '%%%s%%'.

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

chx’s picture

Why this is useful? You can join on permissions now. Like join to a new menu table...

Anonymous’s picture

FileSize
9.92 KB

new version attached that fixes a typo in system.install that was causing duplicate key errors after applying this patch.

Anonymous’s picture

one thing this patch made me wonder: should module permissions be setup at install time?

if the module install system set up the permissions via calls to hook_perm, there'd be no need for the loop through hook_perm and the $stored_permissions $new_permissions comparison stuff.

is this completely off track? it seems like this patch is heading away from loading all the permissions from module files, but maybe i'm missing where this is going.

chx’s picture

Status: Needs review » Needs work

Justin is right. I am setting to code needs to work because I will need to deprecate hook_perm and move them to .install files.

chx’s picture

Status: Needs work » Needs review

Upon further consideration, there are two problems which tells me it does not worth to get rid of those six lines or so:

  • can't use raw SQL because you want permissions to be translatable.
  • need to handle the case when there is a new version of module with more or less permissions.
hunmonk’s picture

token comment to get me subscribed to this thread...

Dries’s picture

* can't use raw SQL because you want permissions to be translatable.

We'd store untranslated permissions, just like we do now. Is that a problem?

* need to handle the case when there is a new version of module with more or less permissions.

What do you mean with that? How does that differ from the current behavior?

hunmonk’s picture

We'd store untranslated permissions, just like we do now.

yep, that should work.

need to handle the case when there is a new version of module with more or less permissions.

i'm pretty sure a module's .install file can handle the necessary database updates to make this work

chx’s picture

The problem here is that extractor.php needs to find the permission strings.

I have a new idea: I create a primitive API, two functions, drupal_add_perm and drupal_remove_perm. Then extractor.perm can extract foo from drupal_add_perm ('foo'). This would go into install.inc so you can handle installs and updates as well.

kkaefer’s picture

To solve the translation issue, we could add a @translation "Here is the string." to the PHPDoc comments of the function and extend extractor.php to include these. Then we store the plain english string in the database and t() it on run-time.

drumm’s picture

Version: x.y.z » 6.x-dev
Status: Needs review » Needs work

Looks like this needs work.

bdragon’s picture

Assigned: chx » bdragon

Assigning to self as per convo in #drupal

killes@www.drop.org’s picture

Note that I am not hapy with the proposed solution. Currently, the query that gets a user's permission is run only once. Now it will get run once per permission that is checked on a page.

We should at least check the average amount of queries this will add to Drupal per page and/or think about a caching solution.

chx’s picture

Gerhard, caching is definitely a useful add-on that's possible.

Also we can run the query once and retrieve all perms of a users into a static map... it's possible that's faster than unserializing. There are many possibilities once this is done.

bdragon’s picture

FileSize
13.62 KB

Here is a !!work in progress!!.
I haven't finished rewriting user_admin_perm yet, so it's kind of crap now..

MySQL install: tested
Postgresql install: untested

Upgrade path: not yet

moshe weitzman’s picture

yes, needs benchmarking. this was tried once before without success but i can't find the issue. anyway, all has changed since then.

once nice byproduct of this patch is that we have a logical place to put permission descriptions - http://drupal.org/node/30984

bdragon’s picture

FileSize
14.17 KB

Still no love for the administer roles page, but I split out the table update thingie into a proper function and cleaned up my spacing and removed a couple of lines. Now it should update permissions when you enable a module.. (Tested by truncating permissions table and enabling comment module)

bdragon’s picture

FileSize
17.18 KB

And here's the access control page done the other way around, by permission instead of role.

Thoughts?

webchick’s picture

Patch no longer applies...

bdragon’s picture

FileSize
17.39 KB

Rerolled

bdragon’s picture

FileSize
16.53 KB
  • Load all permissions for a user on the first call to user_access.
  • Move the permission management functions to user.module. (Ideas for better names welcome!)
  • Change algorithm for finding new permissions, use a lot less queries...
drumm’s picture

Can we properly prefix the new tables with 'user_'?

webchick’s picture

Hmmm. That would give us user_role and users_roles, wouldn't it? Sounds confusing. And then the joining table between user_permission and user_role becomes user_permission_user_roles. :P I think the permission and role names are fine, personally...

bdragon’s picture

In *MY* opinion, the most logical place would be a module called "role.module" marked as an essential module.... Then prefixing the tables with role_. ;-)

drumm’s picture

Modules are not for code organization. They are for feature organization. Roles and permissions certainly belong in the user module.

If you want additional code organization, add modules/user/role.inc to be included by user.module as necessary.

bdragon’s picture

Status: Needs work » Needs review

The last patch was technically CNR, but I forgot to set the status...

Dries’s picture

I don't understand what this patch buys us -- except a lot more code and a potential performance penalty.

What problem are we trying to fix, and why are we trying to fix it this way?

chx’s picture

The problem is that you need to access check every existing menu link on every page visit. For example, if role1 has 'administer comments' permissions but no other administer permissions then Drupal 5 and before will display the [comments | admin/content/comment] link , what's even worse they will show it on the first level. The new menu system is totally incapable of this. We just need something to join against.

The performance penalty is unlikely, one user has very few permissions usually. Yes, there is a bit more code, I can't help that.

asimmonds’s picture

Status: Needs review » Needs work

- adding a new role does not use db_next_id(), new role rid is always 0.

- viewing admin/user/access warns:

warning: array_merge() [function.array-merge]: Argument #1 is not an array in \modules\user\user.module on line 1885.

Line #1885 is: '#header' => array_merge(t('Permission'), $role_names),

Should probably use a array cast, or something like:
'#header' => array_merge(array(0 => t('Permission')), $role_names),

- in user_update_permissions(), the:
db_query("INSERT INTO {permission} (pid,perm,module) VALUES (%d,'%s','%s')",db_next_id('{permission}_pid'), $permission, $module);

Isn't required anymore as the user_permission_add_permission() call in the line before already adds the permission if it doesn't exist.

- The patch needs a bit of a tidy (mainly spacing) to meet Drupal coding standards.

chx’s picture

Status: Needs work » Closed (won't fix)

menu happened w/o this.

dww’s picture

Version: 6.x-dev » 7.x-dev
Status: Closed (won't fix) » Active

@Dries #27: I don't understand what this patch buys us.

It makes it much easier to manipulate the permissions associated with a role, directly via SQL queries, instead of grabbing a huge string and doing string manipulations with comma magic to make it work correctly. For example, the DB updated needed over at http://drupal.org/node/153998 is about 10 times more complicated that it should be since we just store a pile of permissions in a big string.

Plus, as moshe pointed out in #16, we could store (and therefore display) descriptions for each permission, which would be a *HUGE* usability win.

So, I think it's a mistake to "won't fix" this issue just because it didn't end up being a critical requirement for the D6 menu system. We should reconsider this once development opens up again for D7. As a developer, it really is *quite* a pain in the ass to manipulate roles and permissions programmatically while they're all smooshed together in a comma-separated string.

chx’s picture

Status: Active » Needs work
webchick’s picture

I might make this my first D7 patch. We'll see.

webchick’s picture

Nevermind. My itch is over at http://drupal.org/node/30984. But big +1 to anyone who chooses to take this on. :)

Senpai’s picture

Subscribe. I'm tracking this against my own hook_configuration() issue at node/230059

bdragon’s picture

I told chx I'd work on this after I got better. (Got the flu. :-/)

Still not better, but I spent a bit of time tinkering with this today.

bdragon’s picture

I probabaly shouldn't be coding while feeling like this, but whatever.

Here's a re-think of things.

I left some notes and questions in the patch marked with @TODO.

Anonymous’s picture

FileSize
12.7 KB

updated patch to make it apply to cvs HEAD.

i think some of the changes have already been committed.

Anonymous’s picture

tested this on a fresh HEAD install, i can create, edit etc roles and permissions. so, looks good to me. some questions:

1. why aren't we keeping the permissions table, and using role_permission as a lookup on the id's?

2. why the blind deletes and inserts in user_admin_perm_submit? we have access to current role/permission set in $form, so maybe only write to the db for things that have changed?

3. if we keep the permission table, should we write to it at module install time and record the module name? then we can avoid all the calls to module_list => module_invoke stuff just to get the list of perms and what module they belong to

Anonymous’s picture

asked Bdragon about questions in #41 in #drupal IRC:

(09:49:14 AM) Bdragon: beejeebus: Still sick, too many things flying around in my head, don't have an opinion...

so, anyone else have an opinion? i'll work on a patch along the lines i suggest in #41, but i'd like some feedback.

Anonymous’s picture

FileSize
12.57 KB

rerolled patch against HEAD, no other changes.

Anonymous’s picture

FileSize
12.57 KB

rerolled patch against HEAD, no other changes.

still looking for feedback on the questions i raised in #41...

Crell’s picture

Status: Needs work » Needs review

+1 on concept. Some visual review of the patch, although I've not tried running it yet:

+  // Copy the permissions from the old {permission} table to the new {role_permission} table.
+  $result = db_query("SELECT rid, perm FROM {permission} ORDER BY rid");
+  while ($role = db_fetch_object($result)) {
+    $permissions = explode(', ', $role->perm);
+    foreach ($permissions as $permission) {
+      db_query("INSERT INTO {role_permission} (rid, permission) VALUES (%d, '%s')", $role->rid, $permission);
+    }
+  }

It's more robust to explode on just the comma and use array_map('trim', $array) on the result, like so:

$permissions = array_map('trim', explode(',', $role->perm);

Regarding some of the TODOs: Yes, we want to be able to show/update just a subset of permissions/roles, so the current code there is correct and the TODO can be changed into a normal doc explaining why we do it that way. That said, there may be a more efficient way to do the delete/insert cycle by deleting just the roles/perms that were shown and rebuilding that. There should be fewer delete queries that way. (OTOH, it's not like this is a particularly common page so a few extra queries isn't a real problem.)

I don't think for the dataset we're working with the DISTINCT will make any appreciable difference, and neither will the duplicate iteration, so it doesn't really matter which way we go, performance-wise. I personally prefer the DISTINCT myself, but that's just stylistic.

Regarding #41:

1) I am not sure I follow. Which IDs are we looking up?

2) The delete/rebuild cycle is a very common idiom for this sort of task, although as I note above it could probably be optimized a little. Not a huge issue given how rarely that form is executed.

3) We need to know the full list of modules/permissions quite rarely, so the cost of the module_invoke_all() is not a problem. Besides, that should get faster soon anyway. :-)

Setting to CNR so that others know to try it out.

Anonymous’s picture

1) I am not sure I follow. Which IDs are we looking up?

not look up ids, use ids to link roles and permissions like users_roles and pretty much any other normalised many-to-many table relation. costs us little, and dww's comments in #31 sum up why i think this is a good idea.

as for 2) and 3), i care a lot less about these than i do 1).

pwolanin’s picture

After some discussion with justinrandell and webchick in #drupal, here's what I hope is a patch that's pretty much the minimum change needed to give us this better table structure.

I think a couple core tests need to be patched too - but feedback on this patch appreciated before I make that effort.

pwolanin’s picture

Assigned: bdragon » pwolanin
FileSize
29.13 KB

This patch mostly fixes the tests, but I can't really be 100% sure yet, due to this ciritical error: http://drupal.org/node/252920

Anonymous’s picture

tested this patch, and it works as advertised.

i was expecting this patch to introduce {role_permission} as a lookup table, just storing the rid and pid, and keeping {permissions} and {role}. having a description column in permission seems a logical place to keep permissions descriptions, so we probably don't want too denormalise too much?

@pwolanin: sorry i didn't make this clearer on #drupal.

pwolanin’s picture

@justinrandell - since all the permission handling is based on the strings, I think it would just be a PITA to link it to another table. Storage of a few short strings doesn't cost us much and makes all the queries simple and obvious.

Anonymous’s picture

@pwolanin: well, i guess we disagree on the trade offs :-)

i'd still prefer to see this done via a lookup table and store the strings from hook_perm in the db, but if i'm the only one, this patch is still a step forward, so i'm +1 as it is now. anyone else got an opinion?

pwolanin’s picture

so, chx says (paraphrasing) in #drupal "the table structure is good if the benchmarks are good"

A quick test looks fine. I used ab with a session cookie for a normal user (not user #1) across the local net. The test machine is running PHP 5.2.4, MySQL 5.0.45, Darwin 10.4.11, apache 1.3.41

I ran each test 2x, so you can get a sense of the variability.

Without patch:

$ ab -n200 -v1 -c5 -C SESS***=*** http://192.168.1.2/drupal-7/admin

Time taken for tests: 31.387279 seconds
Requests per second: 6.37 [#/sec] (mean)

Time taken for tests: 31.437647 seconds
Requests per second: 6.36 [#/sec] (mean)

$ ab -n200 -v1 -c5 -C SESS***=*** http://192.168.1.2/drupal-7/admin/user/permissions

Time taken for tests: 27.789632 seconds
Requests per second: 7.20 [#/sec] (mean)

Time taken for tests: 28.213168 seconds
Requests per second: 7.09 [#/sec] (mean)

WITH patch from #48:

$ ab -n200 -v1 -c5 -C SESS***=*** http://192.168.1.2/drupal-7/admin

Time taken for tests: 30.527532 seconds
Requests per second: 6.55 [#/sec] (mean)

Time taken for tests: 30.688854 seconds
Requests per second: 6.52 [#/sec] (mean)

$ ab -n200 -v1 -c5 -C SESS***=*** http://192.168.1.2/drupal-7/admin/user/permissions

Time taken for tests: 27.980566 seconds
Requests per second: 7.15 [#/sec] (mean)

Time taken for tests: 27.727507 seconds
Requests per second: 7.21 [#/sec] (mean)

So, bottom line is that I see the same or a slightly faster benchmark with the patch.

pwolanin’s picture

here's a revised patch that is exactly the same except for omitting the changes to profile.test - there is a patch here which fixes that: http://drupal.org/node/252920#comment-827565

With the combination of this patch with that one, tests work.

pwolanin’s picture

the profile.test fixes are committed.

Re-rolled to clean up some minor whitespace problems, plus add more return output during the update.

Dries’s picture

I reviewed the code and it looks RTBC. My tests ran fine with it.

I wonder if it makes sense to add some additional tests to test the "permission API". As far as I could see, we don't test permissions being revoked/deleted. Breaking the permission API might results in all kinds of security issues. It seems like an area that we'd want to test well. This might be a good time to go the extra mile and to write some more tests and/or clean up the API if necessary.

pwolanin’s picture

ok, we; there's not really a permissions API to add/remove them except the big form. I'll see about writing a functional test for that page since it does not seem that there is any such test currently.

pwolanin’s picture

Also, thinking a little more about the patch this morning, it seems that perhaps the update should be in system.install rather than user.install. Normally I'd want to keep core updates with the respective module, but if I recall correctly, system.install's updates are always run first. Thus, any other module updating its permissions could use the new, easier table structure for updates if the update is done there.

Crell’s picture

We should probably introduce a proper API for playing with the permission settings; that would help install profiles, too, and make it more testable. Whether we do that as part of this patch or not I don't know.

Dries’s picture

Thanks Peter, that would be helpful.

pwolanin’s picture

Status: Needs review » Needs work

setting to CNW until I have a chance to rewrite the update and get a basic test in place.

pwolanin’s picture

partly done

pwolanin’s picture

Status: Needs work » Needs review
FileSize
17.63 KB

Seems to be working - update moved to system.install, basic test added to user.test

Anonymous’s picture

Status: Needs review » Needs work

two comments on user_role_permissions:

1. this looks like a typo:

if (isset($role_permissions[$rid])) {
  $role_permissions[$rid] = $stored_permissions[$rid];
}

shouldn't it be:

if (isset($stored_permissions[$rid])) {
  $role_permissions[$rid] = $stored_permissions[$rid];
}

2. why the LEFT join? don't we want an INNER join there?

Dries’s picture

I'm not 100% on all the different layers of caching, and caching in static variables in particular. It's prone to errors. It is becoming impossible to remember when you need to flush what cache.

pwolanin’s picture

@justinrandell -

#1 - yes, that may be a bug.

#2 - I think a LEFT JOIN is preferred, since even if a role has no permissions we want to get a results back. Otherwise we keep issuing a query.

pwolanin’s picture

Status: Needs work » Needs review
FileSize
17.63 KB

fixed #1

confirmed correctness of LEFT JOIN w/ davidstrauss.

Anonymous’s picture

LEFT JOIN: ah, good point. wasn't thining about it from the point of view of the cache.

attached a new patch that just updates this code for HEAD - no functional changes.

i think this is RTBC, but i'll wait for others to confirm.

pwolanin’s picture

oops- clashed with registry patch - re-rolled

pwolanin’s picture

also, davidstrauss suggested in IRC that the query could alternately use an INNER JOIN, but then we would need to change the loop logic in function user_role_permissions() to set empty entries for any rid that returns no results.

floretan’s picture

Tested patch from #68 (the patch from #67 works too and is probably identical, but I haven't taken a close look at the code).

The new test class UserPermissionsTestCase looks good, I think it covers what we need for this patch to be committed. Tests are run without any failures or exceptions.

The rest of the changes seem ready as well, but a little more attention wouldn't hurt for a patch of this importance, so I'm leaving it as CNR.

pwolanin’s picture

After further thought- maybe the INNER JOIN is the better choice. The code for that function seems even a little simpler this way.

Dries’s picture

Status: Needs review » Fixed

Reviewed, tested, looks great. Committed to CVS HEAD. Thanks Peter.

dww’s picture

Status: Fixed » Needs work

Wow, I go away for a little while and look at all the goodness that happens. ;) Cool.

However, it seems like this deserves a mention over at http://drupal.org/node/224333, no?

chx’s picture

Derek, I can't see any API change that would need documentation. Please enlighten me.

pwolanin’s picture

@chx - I agree generally module developers won't need to take any action. However, it's probably worth adding a short section for those who are changing their module's permissions, or for any modules that try to directly manipulate permissions.

moshe weitzman’s picture

seems to me that some functions that are in the test ought to be in core. for example:
drupalCreateRole
drupalCreateUser

catch’s picture

Moshe: those functions are modified by the patch but they're already in core.

I reckon a note to explain the change ought to be fine for this, but agree it's worth having one just in case.

dww’s picture

As has been pointed out elsewhere, an "API" is more than a set of function names and parameters. It's also the underlying data schema, how the data is stored, used, etc. Given that there is no "API" for manipulating roles and permissions with functions, module authors have only had the option of directly manipulating the DB tables. Those tables are now different, so the upgrading from 6.x to 7.x doc needs to mention this.

2 general points:

1) I'd much rather there was "too much" info in the upgrade docs and we mentioned things that will only bite a small handful of users (or folks upgrading custom code for their own sites), instead of not mentioning changes that "shouldn't" effect people (which inevitably will).

2) If there's any chance of a change breaking someone's code, no issue should be marked "fixed" until it's documented in the upgrade guide. This is an extremely small price to pay for our drop-is-always-moving philosophy. But, given that philosophy, this must be a hard and fast rule, (dare I say it) even more strictly enforced than "not committed without the tests".

Cheers,
-Derek

chx’s picture

Status: Needs work » Fixed

Added a note.

dww’s picture

@chx: Thanks. ;) I would have just done it myself, but I wanted to make the more general point...

pwolanin’s picture

@chx - thanks, I just didn't have time the last few days for much of anything.

Anonymous’s picture

Status: Fixed » Closed (fixed)

Automatically closed -- issue fixed for two weeks with no activity.