Problem/Motivation

Currently module allows to masquerade to other users that have ANY of allowed roles (set in role permissions)
But the weight of role is not respected, so allows to masquerade as user with roles that has higher weight of roles
That allows to masquerade as Administrator role if that user has ANY other allowed roles

Proposed resolution

Hardening the check for roles by taking into account role weight.
That means:
if user(which we going to masquerade) has roles that current user has no access no access should be granted

Remaining tasks

User interface changes

API changes

Data model changes

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

andypost created an issue. See original summary.

andypost’s picture

Priority: Normal » Major
mbovan’s picture

The last submitted patch, 3: negate_role_permissions-2688499-3-TEST-ONLY.patch, failed testing.

Status: Needs review » Needs work

The last submitted patch, 3: negate_role_permissions-2688499-3.patch, failed testing.

mbovan’s picture

Status: Needs work » Needs review

Unrelated test fails are getting fixed in #2448707: Fix masquerade tests

Berdir’s picture

+++ b/masquerade.module
@@ -136,19 +136,13 @@ function masquerade_masquerade_access($user, UserInterface $target_account) {
+  $target_roles_access = [];
   foreach ($target_account_roles as $role_id) {
-    if ($user->hasPermission('masquerade as ' . $role_id)) {
-      if ($role_id === UserInterface::AUTHENTICATED_ROLE) {
-        // Make sure that this is the only role user has to prevent collision
-        // with 'masquerade as any user' permission.
-        if (count($target_account_roles) == 1) {
-          return TRUE;
-        }
-      }
-      else {
-        return TRUE;
-      }
-    }
+    $target_roles_access[$role_id] = $user->hasPermission("masquerade as $role_id") ? TRUE : FALSE;
+  }
+  // Only allow masquerade if a user has access to all the target account roles.
+  if (!in_array(FALSE, $target_roles_access, TRUE)) {
+    return TRUE;
   }

It might be easier to do a return FALSE in case one permission is missing?

Also, I'm wondering what "masquerade as authenticated" then exactly means in this context.

Probably allow to masquerade as user that does not have any other permission. But do we require that permission explicitly if a user has roles, do you always need authenticated and the other? seems a bit weird? Maybe we could special case that, if a user only has that role, we specifically check for that permission, else the loop with return false if !hasPermission()?

mbovan’s picture

It might be easier to do a return FALSE in case one permission is missing?

I thought the same until I saw the doc block:

/**
 * Implements hook_masquerade_access().
 *
 * This default implementation only returns TRUE and never FALSE, since
 * alternative access implementations could not work otherwise.
 */
Also, I'm wondering what "masquerade as authenticated" then exactly means in this context.

Probably allow to masquerade as user that does not have any other permission.

Yeah, it allows masquerading as normal users, users who have no roles (authenticated role by default).

Berdir’s picture

well, then we can simply return NULL instead of FALSE, although I find that a bit strange :)

also, if it's apparently using the true/false/null pattern then it should be using AccessResult but that's a too big change for this issue obviously.

mbovan’s picture

Berdir’s picture

Ok, that doesn't include a special case for authenticed permission yet, but lets see what @andypost thinks if and how we need that.

I think in almost all cases, you will need to select "masquerade as authenticated" permission anyway because you want to authenticate as users without additional roles as well. Means you can't configure to masquerade only users with a specific role and *not* those that are just authenticated. There might be some use cases around that..

mbovan’s picture

Users with a specific role are technically specific role + authenticated role users (at least by the default implementation of Drupal\user\Entity\User::getRoles()), which is a bit misleading in UI as "authenticated" role is not displayed...

niko-’s picture

Priority: Major » Critical
andypost’s picture

Assigned: Unassigned » deekayen
Status: Needs review » Needs work

I totally agree that it needs better protection!
IMO it needs more work to fix "any" case

  1. +++ b/masquerade.module
    @@ -137,19 +137,13 @@ function masquerade_masquerade_access($user, UserInterface $target_account) {
    -        // Make sure that this is the only role user has to prevent collision
    -        // with 'masquerade as any user' permission.
    

    this check is lost

  2. +++ b/masquerade.module
    @@ -137,19 +137,13 @@ function masquerade_masquerade_access($user, UserInterface $target_account) {
    +    if (!$user->hasPermission("masquerade as $role_id")) {
    +      return NULL;
    

    I think better to compare max role weight of users for protection

  3. +++ b/masquerade.module
    @@ -137,19 +137,13 @@ function masquerade_masquerade_access($user, UserInterface $target_account) {
    +  // Only allow masquerade if a user has access to all the target account roles.
    

    looks too strict

Berdir’s picture

1. kind of, but it's a bit different than before. the question is wheter we want to allow masquerade to *only* a specific role but not authenticed users without a role. I guess so. that's also what the 7.x patch does I think. lets extend that tests that a user has masquerade as some permission but not authenticated can authenticate as a user with that permission but not as one that does not have any permissions other than authenticated.

2. & 3. Nope, as we discussed, role weight is pretty much meaningless, it's not possible to rely on it. that's how it has to work to be secure. However, we possibly need to check documentation and explain that a user must have permission for all the roles that another user has.

andypost’s picture

Assigned: deekayen » Unassigned

#2&3 just needs hook_help() additions to explain expected behavior

johnchque’s picture

Rebasing against latest dev version.

andypost’s picture

Probably it needs rebase/reroll

This issue is a main blocker to stable release

mpp’s picture

I'd suggest to change:

'Users with the Masquerade as any user permission are able to masquerade as someone else. Users may only masquerade if they have the same or more permissions as the user they want to switch to. Users are never able to escalate their privileges by masquerading as someone else.'

to:

'A user may only masquerade as another user if he has the Masquerade as any user permission or if he has all the Masquerade as @role permissions for all the target account roles.

frob’s picture

A user may only masquerade as another user if they have the "Masquerade as any user" permission or if they have all the "Masquerade as ROLE" permissions for all the target account's roles.

I think the suggestion makes it more accurate. I just de-gendered the sentence and removed developery "@role" to try and make it a bit more site-builder accessible.

sasanikolic’s picture

Updated the help text with the latest suggestion above from @frob. I slightly adapted it however, since "A user" is he/she and afterwards you used "they". Here is the patch with the updated help text.

Senthil Kumar Kumaran’s picture

This is a very critical issue. Thank you for maintainers & supporters addressing this.

Any update on when this will be rolled out with module update?

Best,
Senthil Kumar Kumaran

andypost’s picture

Job shows for 8 new coding standard issues https://www.drupal.org/pift-ci-job/1534337

Berdir’s picture

We do have to make it a bit inconsistent with existing code to avoid these, defined the properties separtely and made them camelCase.

andypost’s picture

Status: Needs review » Reviewed & tested by the community

Thanks, gonna commit and create new release

  • andypost committed db787eb on 8.x-2.x authored by Berdir
    Issue #2688499 by mbovan, Berdir, sasanikolic, yongt9412: Negate role...
andypost’s picture

Version: 8.x-2.x-dev » 7.x-1.x-dev
Status: Reviewed & tested by the community » Patch (to be ported)

Thanks everyone for contribution!
this could be backported to 7.x

sasanikolic’s picture

+++ b/tests/src/Functional/MasqueradeWebTestBase.php
@@ -37,14 +37,35 @@ abstract class MasqueradeWebTestBase extends BrowserTestBase {
+   * Supoer User.

Uh oh, we missed a typo here. :)

  • andypost committed d8d133a on 8.x-2.x
    Issue #2688499 by sasanikolic, andypost: fix typo
    
andypost’s picture

@sasanikolic thank you! fixed)

wturrell’s picture

I think it'd make sense to add a note/change record about this to the Release Notes page for beta3 - I had a client contact me after masquerade stopped working (although in Permissions I'd selected the roles they needed to masquerade as, such as one called 'Applicant' which most accounts on the site have, I hadn't ticked "Masquerade as Authenticated User"). And I hadn't picked it up myself as I'd only been testing as user 1.

I suspect quite a few people could have ticked custom roles but NOT "as Authenticated User".

gngn’s picture

wturrell in #33:

I suspect quite a few people could have ticked custom roles but NOT "as Authenticated User".

Yes, we had the exact same problem on two sites: masquerade stopped working because we had not checked "Masquerade as Authenticated User" (which worked with beta2).

And since this issue is 7.x-1.x-dev (#29 changed the version for backport) it is harder to find if looking for 8.x issues.

So we agree with the idea of adding an explicit note to the 8.x-2.0-beta3 release notes (something more than just linking "#2688499 by mbovan, Berdir, sasanikolic, yongt9412: Negate role permissions").

andypost’s picture

@wturrell @gngn any suggestion to write in change record?

gngn’s picture

I found out that a masquerading user now really needs all the "Masquerade as ROLE" permissions for all the target account's roles.

With beta2 it was enough to have just one matching permission (besides "as Authenticated User").
This is also mentioned in the change of the help text.

So maybe something like:

If you are using "Masquerade as ROLE" permissions check if you have permissions for all the "Masquerade as ROLE" permissions for all the target account's roles (in beta2 it was enough to have just one matching permission (besides "as Authenticated User").

gngn’s picture

Maybe better:

If you are using "Masquerade as ROLE" permissions check if you have permissions for all the "Masquerade as ROLE" permissions for all the target account's roles including "as Authenticated User".
In beta2 it was enough to have just one matching permission (besides "as Authenticated User").