Problem/Motivation
In \Drupal\user\Access\RoleAccessCheck if the user has no roles
Which is hypothetical scenario, confirmed with Security team that, it would never happen in real life.
A user will at least have an anonymous role at minimum.
However, with testing if we have a user without a role the AccessResult::neutral() is returned.
Steps to reproduce
It is not possible to re-produce with a Drupal setup, but only in tests.
To Reproduce:
- In \Drupal\Tests\Core\Route\RoleAccessCheckTest
- Add a user with no role
- Add a test of user with no role
- The test will fail.
Proposed resolution
Tighten the code so that if a user does not have a role, return AccessForbidden.
Remaining tasks
Review
Commit
User interface changes
None
API changes
None
Data model changes
None
Release notes snippet
| Comment | File | Size | Author |
|---|
Issue fork drupal-3249027
Show commands
Start within a Git clone of the project using the version control instructions.
Or, if you do not have SSH keys set up on git.drupalcode.org:
Comments
Comment #2
larowlanComment #3
cilefen commentedNovice?
Comment #4
beatrizrodriguesComment #5
beatrizrodriguesHi, I will work at this problem that you talked about @amjad1233 but I think that maybe we have to open another issue for the modification of web/modules/contrib/search_api/tests/src/Kernel/Processor/RoleAccessTest.php test as it is a file that belongs to a module that is not from the core. I understand that altering this file is a mean of testing that change about the role access, but I think it can not be sent in a same patch, so that's why I think you could open another issue and add here as a related issue.
Comment #6
amjad1233Hi @beatrizrodrigues
Apologies for wrong path in there the actual path is core/modules/user/src/Access/RoleAccessCheck.php & core/tests/Drupal/Tests/Core/Route/RoleAccessCheckTest.php
I have updated the issue summary.
Comment #7
beatrizrodriguesSo, I tried a lot of things at this issue and I couldn't be able to reproduce the problem. I will explain in topics my steps:
1 - I altered the test provider to look like this: (I also added an other parameter to the testRoleAccess function that is called "neutral_accounts" that reefers to accounts with no user roles. )
At 'role_test_4', I'm providing $account_none (an user with no roles) to be verify with the following code:
I noticed that this part that I did, it is just the same as the $deny_accounts' foreach.
That scenario did not return me any errors, the array_diff() function dos not return empty when it is comparing with a account with no roles, it return us the roles that the route allows. So, at the end, the following return is given:
return AccessResult::neutral()->addCacheContexts(['user.roles']);Just like it had to be.
2 - I also tried a scenario where I created a route without roles, like this:
And put in the provider the following line:
['role_test_7', [], [], [$account_none]],So, this time, the result is also AccessResult::neutral().
I don't know if I'm missing something, but it could be nice if you could give me more information about that.
Thank you, and I'll submit a patch in case someone wants to verify the things I said before.
Comment #8
beatrizrodriguesComment #9
beatrizrodriguesComment #10
beatrizrodriguesComment #11
ranjith_kumar_k_u commentedFixed CS issues
Comment #13
benjifisherHere is a little more context than the issue summary gives from Drupal\user\Access\RoleAccessCheck:
So the referenced code is executed when the
_rolerequirement has at least one comma. (More precisely, when there are at least two non-blank parts separated by a comma.) The issue summary states,There are various things that might count as "without a role":
$account->getRoles()might be an empty array, boolean FALSE, or NULL, for example.An empty array leads to the array diff being the same as
$explode_and, which is not empty.A value of NULL or FALSE leads to a PHP warning, but then
array_diff()returns NULL.Looking at \Drupal\Tests\Core\Route\RoleAccessCheckTest, we already have
and that account is granted/denied access as expected. To expose the bug, we should add
We do not need to add any routes to the test, since
role_test_3androle_test_4already have commas in their_rolerequirements.Add to the test that
$account_nulldoes not get access to those two routes. You may need to do something to suppress the PHP warnings.Remember, the goal for now is to get a failing test, to prove the bug. The next step will be to fix the bug, so that the test passes.
Comment #14
benjifisherBy the way, this issue reminds me of a bug report that I opened a long time ago: #1873606: drupalCreateUser() creates a user with Anonymous AND Authenticated roles.. I wonder if that is still a problem.
Comment #16
alanmoreira commentedI'll work on this
Comment #17
alanmoreira commentedCouldn't make any progress :/
Comment #18
damiaosj commentedHello, I'll try to work on this.
Comment #19
damiaosj commentedHi! So, I have made this patch that applies the tests and that reproduces the bug we are having.
I still trying to fix the error but I'll try do a new patch to that.
Comment #20
damiaosj commentedHi! Finally I think I got it!
I've made a new patch, that time with fixes in the tests and the fix to the bug :D if someone could review it will be very helpful!
Comment #21
damiaosj commentedComment #22
damiaosj commentedOh sorry! I forgot to do the coding standards. Assigning back to me to fix this.
Comment #23
damiaosj commentedMade it! A new patch, now following the coding standards!
Moving to needs review!
Comment #24
michelecris commentedHi!
I'll try to review.
Comment #25
michelecris commentedHello,
I applied the patch #23, run the test in \Drupal\Tests\Core\Route\RoleAccessCheckTest and all pass. No errors in the test and no phpcs errors either. So I will change the status for RTBC.
thanks!
Comment #26
alexpottI'm not really convinced by this test. For one passing in NULL for roles is completely breaking the documentation. Secondly the moment we add property typehinting to \Drupal\Core\Session\UserSession::$roles then this bug becomes impossible. At the moment it is highly unlikely because UserSession is nearly always created from \Drupal\user\Authentication\Provider\Cookie::getUserFromSession() or new \Drupal\Core\Session\AnonymousUserSession() - both of which guarantee that UserSession::getRoles() returns an array.
Furthermore I'm not sure that the issue summary has the following correct.
To reach this code $explode_add will contain more than 1 rid and if $account->getRoles() returns an empty array then $diff can not be empty.
Comment #27
alexpottAnd what's more this is granting access when the user has a null value for roles and the route is saying it needs multiple roles to have access. This is would be introducing a security bug.
Comment #30
quietone commentedRemoving the novice tag because of #26 and #27
Comment #32
jayelless commentedDrupalSouth contribution day task.
Comment #34
quietone commentedJust hiding patch files
Comment #35
jayelless commentedBranch form forced forward to be based from current HEAD of main.
Patch updated to identify the error condition that should NEVER occur of an account with NO role defined, and to forbid access in this situation.
Comment #36
smustgrave commentedSeems that the force rebase may have reverted some previous changes.
Thanks.
Comment #37
quietone commentedComment #38
quietone commentedUn-assigning per Assigning ownership of a Drupal core issue.
Comment #39
smustgrave commentedThanks @quietone for fixing up LGTM.
Comment #40
benjifisherI do not remember this issue, but in 2022 I left a comment on the test code, so I reviewed that.
I left a nit-level suggestion on the test. As far as I am concerned, feel free to set status back to RTBC if you accept the suggestion.
If I were updating the code, then I would use multi-line syntax for the arrays in the data provider and the
$this->assertEquals()lines in the test, but that is too big a change to request at this late date.Comment #41
quietone commented@benjifisher, thanks for the review. I have applied the suggestion.
And I agree that the suggested code changes to the test would be nice and should not hold this up. Therefore, restoring the RTBC. I am assuming that tests will pass.
Comment #42
quietone commentedThe failing test is a know random fail, core/modules/settings_tray/tests/src/FunctionalJavascript/SettingsTrayBlockFormTest.php
Comment #43
catchThe actual runtime code changes look good but one comment on the test changes.
Comment #44
quietone commentedI've addressed the feedback from catch.
Comment #45
needs-review-queue-bot commentedThe Needs Review Queue Bot tested this issue. It no longer applies to Drupal core. Therefore, this issue status is now "Needs work".
This does not mean that the patch necessarily needs to be re-rolled or the MR rebased. Read the Issue Summary, the issue tags and the latest discussion here to determine what needs to be done.
Consult the Drupal Contributor Guide to find step-by-step guides for working with issues.
Comment #46
quietone commentedTest failure in core/modules/config/tests/src/Functional/ConfigInstallProfileOverrideTest.php. Only have time to add this.
Comment #47
benjifisher@quietone:
That test passes when I run it locally. I looked at the test: it seems unrelated to the changes for this issue, and I do not see how it could fail. If you can re-run the pipeline, then please do.
Comment #48
quietone commented@benjifisher, thanks for looking at this issue. I reran the test and it passed, so back to needs review.
Comment #49
smustgrave commentedBelieve all feedback for this one has been addressed, least I didn't see anything new.
Comment #50
larowlanSorry folks, I think dropping assert messages in a loop is bad DX - left a comment on the MR
I'd also like to see a follow up to deprecate RoleAccessCheck. We should never be checking access by role, only by permission.
Comment #51
quietone commentedFollow up created, #3616218: Deprecate RoleAccessCheck.
Comment #52
quietone commentedMy error, I was reading one thing and thinking another.
I've added assertion messages.
Comment #53
dcam commentedI did a complete review of this issue since I haven't ever looked at it before. I became concerned about @alexpott's comment in #26 that the description of the bug in the issue summary may not be correct. We also don't have access to the old test result showing the bug as requested by @benjifisher in #13. I examined the
RoleAccessCheckcode and couldn't understand how this bug might occur in the first place.I downloaded the MR and did a test-only run on my local to check the results. Note that the test expects
AccessResult::forbidden(), so a test-only run will always fail. Per the issue summary we expect that it will fail withAccessResult::allowed(). It does not. On the initial run it failed for the user with an empty array of roles,$account_none, withAccessResult::neutral(). I removed$account_nonefrom the test's data provider in order to get results for$account_null, then re-ran the test. This time the test failed repeatedly with aTypeErrorbecause you can't pass NULL toarray_diff()orarray_intersection().In #13 back in 2022 @benjifisher said:
This is no longer accurate. Was this a PHP 7 problem before the array functions got type-hinted parameters? Have we aged out of this issue?
This looks like a "won't fix" to me. As far as I can tell, the problem does not exist as described in the issue summary. If it is still a problem, then once again someone needs to provide a failing test to prove it. Also, the issue summary must be updated in that case. For one thing, the proposed resolution isn't even up-to-date because the fix was changed to return
AccessResult::forbidden().Comment #54
quietone commentedI have updated the issue summary. And it is clear now that this returns access forbidden for the unlikely case when an account has not roles.
It is true, that typehinting will help here, but we don't know when that will happen. So, any improvement should be done now. I am also aware that 2 security team members have reviewed this in the last 3 months and have not suggested this change is not worth making.
Back for reviews.