The attached patch allows users to have more than one role. I have heard requests for this often and think it will offer much greater flexibility/usability to the permission system. Here is a summary of the changes:

  • table changes: new table {users_roles} with only uid, rid fields; all existing uid, rid combinations will be populated in {users_roles} before droping rid column in {users} table in updates.inc; changes also made to database.(my|pg)sql
  • user/edit page: roles are selected using a multiple select box rather than a radio group (>= 1 role is required)
  • delete role: when a role is deleted and a user is in only that role, he is assigned to 'authenticated user'; otherwise, that role is removed and others remain
  • load user: when user information is loaded in user_load() or sess_read(), all role IDs and names are retrieved, so $user->rid and $user->role are both arrays now (the key is the role ID in both)
  • comment moderation/scoring: the highest score of a user's role(s) for a given vote will be used

Open issues:

  • Do we need to keep both $user->rid and $user->role? I would say no. I only left both for backwards compatibility, but it is probably not needed. We can probably simply have $user->rid (or $user->role or $user->roles) as an array of role names with the role IDs as keys.
  • Should we somehow verify that the uid/rid insert from {users} into {users_roles} succeeds before dropping the {users}.rid column in updates.inc?
  • Inform contrib. authors who may be using the $user->rid/role fields in modules or themes.
  • Inform users who may be using $user->rid/role fields in custom blocks, PHP pages, etc.

Thanks Dries, Adrian, and Kjartan for your input and help. :-)

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

moshe weitzman’s picture

Finally! ... Another great patch.

It looks like you are doing what I would have hoped. A user's permissions are the union of all permissions granted to his roles. That is, if a user has editor and authenticated user roles, he will benefit from the permissions granted to editor and from the permissions granted to authenticated user.

About your open issues:
- i like switching to $user->roles so it is clear that users may have more than one role. I don't care much though.

Stefan Nagtegaal’s picture

The patch allows multiple roles per user, but did this mean that if I am administrator, and I give myself also the role of the 'authenticated user', I can switch between these roles? Or do I get both of all permissions?

BTW, just a little question: The default role names ('anonymous user' && 'authenticated user') are not translatable.. Could you also take care of that?

Dries’s picture

Looks good. Here are some suggestions:

  1. I suggest we introduce $user->roles and drop both $user->rid and $user->role.
  2. Some contributed modules will need to be updated. When this patch gets committed, you'll want to document the changes at Converting 4.4 modules to HEAD.
  3. Can't you use a JOIN here?
     $result = db_query("SELECT r.rid, r.name FROM {role} r, {users_roles} ur WHERE ur.uid = '%d' AND r.rid = ur.rid", $user->uid);
    
     $result = db_query("SELECT DISTINCT p.perm FROM {role} r, {permission} p, {users_roles} ur WHERE ur.uid = '%d' AND r.rid = ur.rid AND p.rid = r.rid", $user->uid);
    
  4. In SQL queries, you should not put single quotes around %d's. Here are two examples, but there are many more:
      $result = db_query("SELECT r.rid, r.name FROM {role} r, {users_roles} ur WHERE ur.uid = '%d' AND r.rid = ur.rid", $user->uid); 
       // this query is used at two locations
    
      $rolesresult = db_query("SELECT r.name FROM {users_roles} ur, {role} r WHERE ur.uid = '%d' AND r.rid = ur.rid", $account->uid); 
        // can't we use '$result' instead of '$rolesresult'?
     
  5. Try using single quotes for constant strings. For example, use 'small' instead of "small".
jhriggs’s picture

Stefan -

A user gets the union of all permissions from all of his roles. There is no switching. As for the translations, it may be worth opening another feature/bug for that. I'm not sure what exactly might be affected by that change, and it is probably out of the scope of this issue.

Jim

jhriggs’s picture

Dries -

1. I will submit an updated patch with $user->roles later today.

2. I'll take a look at that page later also.

3. Do you mean use an explicit INNER JOIN rather than that implied by the comma? I can do that. I just tried to be consistent. User.module uses both INNER JOIN and the comma syntax, so I tried to use whatever was already there when I modified a query.

4. This is a habit. I wrap all value types in queries with single quotes as an added measure of protection (i.e. from mischievous user input). There is certainly nothing wrong with it syntactically, but it is not the "standard" for Drupal, so I will change them, I guess.

4a. We can't use $result, because we are inside a while loop that is already using $result.

5. Again, I used whatever was already there. User.module is peppered with both single- and double-quoted string constants. Do you want me to fix them all?

Dries’s picture

Do you mean use an explicit INNER JOIN rather than that implied by the comma? I can do that. I just tried to be consistent. User.module uses both INNER JOIN and the comma syntax, so I tried to use whatever was already there when I modified a query.

Yes, please.

This is a habit. I wrap all value types in queries with single quotes as an added measure of protection (i.e. from mischievous user input). There is certainly nothing wrong with it syntactically, but it is not the "standard" for Drupal, so I will change them, I guess.

IIRC, it breaks PostgreSQL compatibility.

Again, I used whatever was already there. User.module is peppered with both single- and double-quoted string constants. Do you want me to fix them all?

I know there are a lot of double-quoted strings left, but we're trying to update them step-by-step. You don't have to update them all, but it would be nice if you could update some (eg. the ones in the code you modified).

Thanks Jim.

jhriggs’s picture

OK. Patch v2 attached. This version uses only a $user->roles array. The keys are role IDs, and the values are role names. I removed the single quotes from %d in queries, and made all INNER JOINs explicit. Finally, I changed all constant strings with double quotes to single quotes in the places I made changes, as requested.

Dries’s picture

First, after applying this patch, update.php is broken. This is because the upgrade scripts attempts to load the current user so it can perform an access check. To work around this problem the user will have to create the users_roles table manually before running the upgrades. I suggest we add instructions to update.php's main page like we did for the session table in Drupal 4.3.0 and the system table changes in Drupal 4.4.0.

Secondly, does everyone understand what selecting multiple roles means and how it works? As we're dealing with permissions, there should be no confusion. I think we should communicate clearly how each role's permissions are 'merged' when multiple roles are selected. I'd add that explanation below the role selection menu.

Third, any idea how many additional SQL queries are introduced when, for example, viewing the main page?

Looking better, every time!

jhriggs’s picture

OK. Voici v3. This patch removes the users_roles table creation from updates.inc and moves it to a note in update.php. (I also updated a couple of grammatical things in update.php and made all three upgrade notes consistent.) The patch also adds a description to the Roles field on the user/edit page.

As for the number of queries, there is only one additional query in user_load()/sess_read() that retrieves the user's roles from the users_roles table. This is followed by the while loop to retrieve all the results. Permissions are still obtained with a single query, though the results must be retrieved in a loop.

Dries’s picture

Committed to HEAD. Thanks Jim H. Riggs. :-)

jhriggs’s picture

FileSize
1.06 KB

As reported by Morbus on the drupal-devel mailing list, the user/edit page is generating a foreach invalid argument error as a result of this patch. The patch did not account for user_save() being called from places other than admin/user/edit. The new, attached patch checks that the user has 'administer users' access and that the rid value is an array before reloading the edited user's roles.

jhriggs’s picture

FileSize
3.13 KB

OK. The patch I made last night was bogus as Kjartan pointed out, so the attached patch should behave the way we want.

Also, Stefan noted that roles are incorrect on a fresh install. The original patch(es) did not completely update database.(my|pg)sql. The rid column was not removed from {users}, and the anonymous user's default role was not inserted into {users_roles}. This patch fixes these issues also.

Dries’s picture

Committed to HEAD. Thanks. Marking this fixed.

Anonymous’s picture