Problem/Motivation
The comment module comes with a CommentAccessControlHandler which is not really used on crucial places. Instead of using this handler, there are dozens of places, where the permissions provided by the comment module are checked directly (e.g. post comment permission checks instead of $entity->access('create') or access comments permission checks instead of $entity->access('view')).
For example, the $entity->access('create') access check is only used during comment link generation, but not for the comment form itself.
Due to this, it is currently impossible to create a custom access control handler for comment entities, as it would not be used where necessary. When reading through the code, you can also see there is a lot of access-related duplicate code (because the permission checks duplicate the access checks of the access control handler).
These changes will affect other issues as well, when those deal with comment access/permissions, but they are very important to get the comment entity access handling correct.
Proposed resolution
- Replace all permission checks with proper
$entity->access()checks. Search for the following permissions:- access comments
- post comments
- edit own comments
The
<em>administer comments</em> permission has been split in #3180177: [PP-2] Replaces usages of 'administer comments' permission w/ access handler checks in order to limit the scope of this issue. - Refactor the comment access control handler
- On "create access" the commented entity and, on comment replying, the parent comment should be passed as context as they might contain valuable information for a custom comment entity access control handler when making the decision.
- Comment-related tests should still work as before, because only the access checking behavior is changed, but not the access requirements themselves
Remaining tasks
Followup #3180177: [PP-2] Replaces usages of 'administer comments' permission w/ access handler checks
User interface changes
None.
API changes
* The current comments access checks behaviour can be altered by swapping the comment access control handler and implementing a custom access logic.
* A new CommentCreationTrait has been introduced to facilitate testing
Data model changes
None.
| Comment | File | Size | Author |
|---|
Issue fork drupal-2879087
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
hctomAdd note about commenting status and its relevancy for entity access checks
Comment #5
andypostComment #6
jonathanshawThis is a significant architectural tidyup with possible BC implications, would be good to have maintainer input early.
Comment #7
darvanenI agree that having this done would be amazing, I have a question to kick off the discussion.
Here's a piece of code from comment.module:
The comment entity is never loaded in this hook, which seems fair enough because the only requirement (as it currently stands) is for the search result to show the number of comments on the node.
I *think* that if we wanted to use
Entity::accesshere, we would need to load all of the comments on the node and iterate through them, checking to see if any of them were viewable by the user.So what do we do?
Perhaps put the access logic into the field handler somehow and alter the output of
$node->get($field_name)->comment_count?Comment #8
larowlanGah, if only that code could die. Does adding a comment count to a search result really add any value :(
Comment #9
darvanenIt's not the only place where comment_count is used to imply access. Here's another example from CommentDefaultFormatter:
Comment #10
darvanen...and I suspect that Drupal.org is using that comment count on our dashboards:
Comment #11
darvanenAnd on search too.
Does it add to the usability of search results? Anecdotally I would say that I have picked search results that have more comments on them from time to time as it can indicate that a more robust conversation can be found on that particular post in a blog or forum. It would be nice if we could kill it but I suspect that would make a number of people rather unhappy.
Comment #12
jonathanshawSeems to me that hasPermission('access comments') is right in the case of #7.
We want to say nothing if the user can't access comments but "0 comments" if they could access (at least some) comments in principle but it just happens that there aren't any yet. So we can't use $entity->access('view') because we want to return true even if there's no specific comment entity to inquire about.
The example does make we wonder if there's some missing "CommentAccess" service(s) that could centralise and DRY some of these esoteric logic checks. Like "does this entity have any accessible comments" which is surely a useful question in more cases than just adding comment counts to node search results. Such a service would also make it much easier for contrib to add more fine-grained comment access control.
Maybe the first thing to do would be to create such a service, then as a second step modify it to use entity access where possible.
Comment #13
darvanenI would agree if the comment_count contained all possible comments, including unpublished ones. Which would of course change a couple of things about how that count is used. By only containing published comments the count is automatically proxying access rights.
Yes. A thousand times yes.
A use-case I am working on for a client right now is that they want more granular comment permissions such as:
I've had to override several routes, some methods on the CommentController, the default field formatter, and the storage just to implement the new permissions (I'm not including the new code for additional actions such as Unpublish). All of this could be contained in CommentAccessHandler or the service you've proposed.
For my education - why a service rather than inclusion in the access handler? Is it because it doesn't relate directly to the comment entity?
Comment #14
jonathanshawBeyond the scope of my knowledge - education needed for me too!
Comment #16
a_grizzli commentedComment #17
a_grizzli commentedI think this issue should go more far, than initially proposed. AccessControlHandler would be needed on different levels: e.g.:
CommentAccessControlHandlerwill need to respect all these levels. Or there should be different pluggable AccessControlHandler, which can cooperate.Comment #18
a_grizzli commentedFirst of all, I think, that we should not force usage of
$comment->accesscreating a comment entityCommentif it does not exist in given context, hence it is an expensive procedure.Secondly, we need to use access method of comments-field
CommentFieldItemListwhen known to proxy to access method of comment entities:So there are results of my scanning and replacing for
hasPermission()calls in comment module:1. Permission 'edit own comments':
Have not appeared in any parts of code accept in access control handler
CommentAccessControlHandler, which is fine.2. Permission 'post comments':
Case A1: When used in context of comment-entity, like in
CommentForm::save, replace by:Check for 'create' access on comment-entity:
Case A2: When used in context of comments-field, but whithout knowing comment-entity, like in
CommentLinkBuilder::buildCommentedEntityLinks, replace by:Check for 'create' access on access control handler without using comment-entity:
Or alternatively:
Check for 'create' access on comments-field, which acts in sense of appending new comment-entity to the list:
Special Case A2.1: Checking 'create' access for authenticeted user role in
CommentManageer::forbiddenMessage. SinceEntityAccessControlHandler(as parent class ofCommentAccessControlHandler) will cache access per uid, it is not possible to load some authenticated user and abuse it for check... So I tried to simulate an general authenticated user by creating a dummy user (UserSession) with uid = '-1', and to check access using that. Thus actually violates uid definition of user, but works fine. Needs review please!Case A3: When used globally without knowing comments-field nor comment-entity, like in
CommentController::replyFormAccess, replace by:Check for 'create' access on access control handler without using comment-entity:
Special Case A3.1: Checking 'create' access for anonymous user role in
CommentItem::fieldSettingsForm. I coped with it creating an anonymous user, which much more stable than Special Case A2.1:3. Permission 'access comments':
Case B1: When used in context of comment-entity, like in
CommentController::replyFormAccess, replace by:Check 'view' access on comment-entity:
Case B2: When used in context of comments-field, but without comment-entity (like in #7), replace by:
Check 'view' access on comments-field, which equals to 'view' comments
Case B3: When used globally without having comments-field or comment-entity in context...
Well, there is no real replacement, since 'view' access check needs comment-entity to run correctly.
Thus is used once in
comment.module::comment_node_update_indexto restrict indexing of comments:IMHO it is basicly wrong way to deny indexing on all comments, only when some users may not be allowed to access them at all.
I think, that all comments should be indexed in general, and then access should be checked later on 'view' oder 'view label' access of node.
For exapmple, do not show such node in search results, when search matches a comment, but there is no access to it.
This will need a general change in displaying search results and should not be treated by this issue.
Comment #19
a_grizzli commentedComment #21
a_grizzli commentedProviding new patch with corrected pathes
Comment #23
a_grizzli commentedCorrecting patch due to test results #22.
Comment #24
a_grizzli commentedSame procedure as every year...
Comment #25
a_grizzli commentedComment #26
a_grizzli commentedCorrecting patch due to testing results of #25:
CommentAnonymousTest=> Bug fixCommentNonNodeTest=> Bug fixCommentLinkBuilderTest=> Adapt test to new requirementsComment #27
jonathanshawThis is impressive work. The basic approach seems sound.
The problems touched on in the discussion about the access control for lists of comments are hard, but it's right to ignore them for now. Hopefully something like #777578: Add an entity query access API and deprecate hook_query_ENTITY_TYPE_access_alter() will give us ways to improve this in the future.
$operation defaults to 'view' but isn't necessarily 'view'.
Can we centralise the 'administer comments' check into a checkAdministerAccess method on the CommentAccessHandler? That would increase consistency, and make it easier to override (e.g. to help with more granular per-bundle access control in future).
Isn't this change unrelated and unnecessary?
Again unrelated. This is a complex patch and getting it reviewed will be really hard; it's important to keep the changed hunks to a minimum.
'create permission'?
Comment #28
a_grizzli commentedIndeed. I would change comment in this case. Method might be used for 'update' operation, too.
Good idea!
Conversion from
$entity->{$field_name}to$fieldis not necessary, but reasonable.$entity->{$field_name}will involveget()method on entity each time. Where$fieldjust reference an object in local scope.But conversion from
->comment_countto->getValue('comment_count')was indeed necessary to passCommentLinkBuilderTesttest. This test case was designed to passsdtClassinstead ofFieldItemListInterface. Hence it was not possible to callaccess()method on it, which lead to failure. I had to adapt this test with mocking all necessary methods. Mocking magic mathod__get()to keep access to property via->comment_countis not possible IMHO.Again, just a small optimization to avoid calling
get()method multiple times.Yes.
Comment #29
andypost@a_grizzli interesting approch, quickly skimed patch and it looks promissing
Not clear how access handlers can replace permissions but I suggest to file new issue to define and document access control on comments, for example formatter doing access checks on comment entity which could not exists (field ui when editing the field default value)
This case and views needs to be covered and not clear how comments could get benefit from #777578: Add an entity query access API and deprecate hook_query_ENTITY_TYPE_access_alter()
Moving implementation makes no sense to me because it will replicate "account->hasPermission()" call for no reason
Comment #30
jonathanshawComment permissions logic is complex and dispersed; centralising it in one place makes it easier to swap it out to meet the needs of a use case. This would also help issues like #1903138: Move global comment permissions to comment-type level which needs to change what permissions are involved in comments.
Comment #31
larowlanI like the approach here, thanks for keeping this moving.
We need some additional docs around 'view only' because it is a special case, and I agree we should probably make 'view' the lowest operation, given its the default. So that would mean view would be used instead of view only, and we'd need a new operation to signify 'view and comment'.
nit: necessary
nit: !==
Comment #32
a_grizzli commented@jonathanshaw You were right! It was bad idea to use
getValue(), since it actually does a completely wrong thing. Seems I was more concentrated on passing test...It might become a little bit more complicated, since
EntityViewDisplay::buildMultipleusesaccess('view', ...);by default instead of 'view and comment' (better 'view or create'). We would need to add aninstanceofcheck especially forCommentFieldItemListhere.Comment #33
th_tushar commentedPatch passed Drupal tests.
Comment #34
a_grizzli commentedI gave it a try. Attached a new alternative patch. And it seems that there were not many changes necessary. Only in:
EntityViewDisplay::buildMultipleCommentDefaultFormatter::viewElementsCommentFieldItemList::accessFurthurmore, old 'view' checks using
$field->access('view', ...);have been unnecessary far-reaching and only have not produced any troubles because of context used in, which is the same reason for little changes to new 'view' operation.However, this change affects a core entity class beyond comments-only scope.
Docs revised in new patch.
Comment #35
a_grizzli commentedAs for #30:
At the moment changing 'administer comments' is not within the scope of #1903138: Move global comment permissions to comment-type level. But 'skip comment approval' is considered. However, it is used only once. Centralising might be a topic for future development in this case maybe (?).
What I am trying to say is, that if we start centralising 'administer comments' we should consider all permissions in accordance with comments in general. Issue summary have to be updated also.
Examining occurencies of 'administer comments'... Like in case of other permissions there are occurencies in all contextes. Please refer to #18:
The solution might be similar to calling
CommentAccessControlHandler::createAccessfor 'create' access:Indeed,
CommentAccessControlHandleralready use 'administer account' checks itself, so bundling it in a public method would be a simple.Same could be applied to 'skip comment approval', e.g.
checkApproveAccessFurthermore, same could be applied for yet not solved (not patched) problem within
comment.module::comment_node_update_index()with 'access comments' permission check, e.g.checkViewAccessComment #36
jonathanshawNo way should we do this. Hardcoding comment specific logic into EntityViewDisplay is not ok.
I've no idea what to propose though.
That's fine. It's good to keep each patch small and simple rather than doing too much at once. Let's leave 'administer comments' out of this patch.
Comment #37
a_grizzli commentedD'accord.
Another possibility would be, to define a new method, e.g.
getLowestOperation()somewhere in scope of FieldItemList object (?). Such method might return 'view' by default and could be overwritten by CommentFieldItemList to return 'view only'. And then to call it in EntityViewDisplay:Maybe a method to explore possible operations would be nice also, like
getOperations().I don't now yet, where it might be placed. Whether in
FieldItemListInterfaceorAccessibleInterface. In any case thus should not be solved in this issue.I propose, to get along with 'view only' operations first and make new issues for operations exploration (in core) and correcting 'view only'.
In this case, I would provide a new patch, which also corrects this:
Comment #38
jonathanshawThat's an interesting idea, but I think it might be good to see what @larowlan says.
Anything that goes beyond comment is massively increasing the scope of this issue so it may be best left to a follow-up issue in order to keep this one tight.
Comment #39
a_grizzli commentedOk.
Nevertheless, here the promised patch, which uses 'view only' consistently as the lowest operation. This will help to change it later by just looking for this term.
Comment #41
jonathanshawComment #42
a_grizzli commentedUps... Overseen to adapt
CommentLinkBuilderTestfor 'view only'. Next try.Comment #43
a_grizzli commentedComment #45
darren ohComment #46
jonathanshawWhat's novice-friendly about this issue?
Comment #47
darren ohPatch failed to apply and needs to be re-rolled.
Comment #48
jonathanshawSo we should be tagging it 'Needs reroll' I think rather than the more general 'Novice'
Comment #49
eelkeblokComment #50
eelkeblokHere's a reroll of 42.
Comment #51
eelkeblokSorry, that patch was wrong.
Comment #52
eelkeblokSome review comments. Many of these are coding standard related nits which you could prevent by installing an automatic coding standard checker. See https://www.drupal.org/docs/develop/standards/coding-standards and possibly also https://www.drupal.org/docs/develop/development-tools
The comment is too long for the line and needs a period at the end.
Comment needs a period.
Some lines in this comment are longer than 80 characters and a period is missing at the end.
The second comment line... you guessed it... needs a period.
Same.
Comment #53
eelkeblokComment #54
vacho commentedOnly patch reroll
Comment #55
eelkeblokSorry, I forgot to remove the tag. I am curious what patch you rerolled and based on what, exactly...
Comment #57
dutchyodaLatest patch didn't apply. I reworked the patch.
Comment #58
webberly commentedim trying to find the right hook to prevent some comments from loading, will this patch help?
Comment #59
darren ohwebberly, this patch should allow you to control who some comments load for.
Comment #60
webberly commentedThank you Darren,
im still not sure the best way to deny access to a individual comment and stop it from being rendered from a module.
There should be a mymodule_comment_access hook.
Comment #61
matsbla commentedFixing feedbacks from #52.
About dependency injection, I don't think it is possible for typed data, there is an issue for it here:
#2053415: Allow typed data plugins to receive injected dependencies
I think it is outside the scope of this issue to fix it.
Comment #62
matsbla commentedComment #64
matsbla commentedI also found another related issue, CommentLazyBuilders builds links always checking access on the default language instead of the specific translation. I've made a new issue and patch for it here: #3134223: CommentLazyBuilders should be language aware
Comment #65
matroskeenI'm using the latest patch and it seems good enough.
However, sometimes for some reason I have orphan comments that brings the following error:
Error: Call to a member function access() on null in Drupal\comment\CommentFieldItemList->lastCommentAccess() (line 109 of /var/www/codebase/web/core/modules/comment/src/CommentFieldItemList.php)Here is a patch with one line changed (interdiff attached).
Comment #66
andypost@Matroskeen this fix is out of scope, see related
Comment #68
matsbla commentedComment #69
matsbla commentedAdd back to NR for #61
Comment #70
larowlanThis is looking good, some observations
We don't use this other than for 'view' operation. So do we need to support the $operation argument here?
Also, do we really want this method to be public? (I think we should keep it internal to the class)
in theory there could be N comments for a particular entity, should we be checking that at least one of them returns access allowed?
e.g. in theory the last comment could be unpublished, which would mean the whole thread was not visible. If that's the case, we're missing some test coverage here
this should use the constant instead of 'authenticated'
is there a perfomance cost for this?
shouldn't this be 'andIf' as per above?
should the administer comments permission also be moved to behind the field access method?
Thanks for all the work here!
Comment #71
claudiu.cristeaRemoved tags:
Here's my review and I'm also assigning to fix the issues from #70 and here:
The comment doesn't reflect the code. Maybe
We shouldn't talk about permission, that's an implementation detail of the access handler.
Same here, let's avoid "permission", we're now using the entity access handler and that could be swapped to ignore the permissions and use its custom logic.
Should be rearranged according to https://www.drupal.org/docs/develop/standards/api-documentation-and-comm...
If this check returns forbidden or neutral, does it make sense to continue, or should we exit early? If we continue, regardless of future checks, the final access cannot be allowed.
Duplicate assignment to
$statusvar.If we, previously, exit on forbidden/neutral, in this point
$accessis allowed. So the first->andIf()makes no sense. And the$entitycacheability should be applied to the result of both checks as both are depending on$entitycacheability.Also, does it make sense to continue if this check results as forbidden/neutral (i.e. either the comments are closed or the user cannot access the host entity)? The following part is about replies but I don't think you can reply to a comment when the comments are closed or you cannot view the host entity. So, I would exit here if this check is not allowed.
Would it make sense to continue if the parent comment doesn't exist? Also, if we exit here we don't need to perform, later, 2 additional checks for
$comment.All these checks can be simplified if we exit early when
$commentdoesn't exist. Also we can process the boolean result of$comment->isPublished() && $comment->getCommentedEntityId() == $entity->id() && $comment->access('view', $account)and use only oneAccessResult::allowedIf(). That would add more clarity.Isn't the
$statuscheck faster?Let's be strict.
Comment #72
claudiu.cristeaI cannot see why this change would bring a performance cost.
I think the approach from the patch is correct. A user granted with such a permission cannot be denied by an access handler. It's kind of "superuser for comments".
I have more remarks on the whole method, in #71:4, 5, 6, 7, 8. I'm proposing a different approach.
I've fixed all remarks except #70.2. I need more time on that.
Comment #73
jonathanshawI added in #41 to get a verdict on #37 / #38, which #70 did not give. Rereading now, it looks like we can ignore it for now, it's possible follow-up material.
The patch in #65 is green, so if #71.4 and #71.6 are valid is there a hole in the test coverage?
Lastly, not sure that this is a bug.
Comment #75
jonathanshawThis issue has plenty of tricky considerations. I'm concerned we might be hindering getting it committed by changing things that are not strictly in scope. In case a committer declines to commit because of this, I'll list here the valid minor changes that could be removed:
Scope creep: Change to strict comparison
All the changes from $entity->get() to $field are technically scope creep.
More scope creep
Again
This refactoring may be right, but isn't it scope creep?
Comment #76
claudiu.cristeaThis strict check made the tests to fail, because
$statusis a string (e.g. "2") whileCommentItemInterface::OPENis an integer (i.e. 2). Also the code has unneeded complexity. Like in the other place, we need a boolean evaluation of$status == CommentItemInterface::OPEN && $entity->access('view', $account)and passing it toAccessResult::allowedIf().@jonathanshaw,
Not sure I get your question. My changes in the
::replyFormAccess()method are only optimisations. For instance, if$field->access('create', ...)returns forbidden, there's no reason to check other aspects, few lines below. We can just exit. Same for the next checks.As now we rely on access check handlers, rather than permissions, I think that
CommentAccessControlHandler::checkCreateAccess()needs additional context, more specific, the commented (host) entity. I'm facing this in the project we're working, where we extendCommentAccessControlHandler::checkCreateAccess()and we add custom logic that should rely on the commented entity. I've enriched the$contextparameter with a newcommented_entitykey.Comment #77
jonathanshawMakes sense. I'm not very familiar with access handlers, but might other operations (e.g. 'view') also like to have this context? Would it make sense to add it whenever getCommentedEntity() is not null, instead of special-casing 'create'?
OK, thanks for explaining that.
Comment #78
claudiu.cristea@jonathanshaw
#75.1: We already touch that line as we switch from permission to access check. It's not out-of-scope to improve the whole line in such cases. There are 2 changes there: (1) the order of the checks was swapped and (2) one of checks was made strict. The 2nd, indeed is wrong because
$statusmight be a string in some circumstances while the constant is always an integer. For (1) see #71.9.#75.2, 3, 4: I'm not the author of these changes but I think I would have done it in the same way. So, we need the
$fieldvariable because now we check$field->access()in more than one place, so it makes sense to capture it into a variable. The other changes, such as$entity->get($field_name)->comment_countto$field->comment_countare just coming from this change. But no strong opinion.#75.5: This started from the @larowlan's question, in #70.4. In fact, almost all the method implementation is affected by the changes in the scope of this issue, see the patch from #65. What I did, I tried to fix #70.4 but than I realised a lot of issues with the proposed implementation and with the current code as well. As we are making an important change in this issue and we touch that method, I think it make sense to optimise it properly.
#77:
Indeed, create is a special case. That's why
EntityAccessControlHandlerInterfacedistinguish between normal "access", when the entity to be checked already exists, and "create access", when we do a check but the entity doesn't exist yet. That's whyEntityAccessControlHandlerInterface::createAccess()was split out ofEntityAccessControlHandlerInterface::access(). In the case of other operations, the handler method (::access()) receives the whole comment entity as parameter, so is able to access the commented entity$comment->getCommentedEntity().Fixed also the coding standards complains.
Comment #80
claudiu.cristea@jonathanshaw the test failure from #76 proved that you were right in #75.1.
Comment #81
jonathanshawWould parent comment also be relevant context sometimes?
I've reread the issue comments in preparation for RTBC ...
AFAIK we are not doing this and this should be removed from the IS?
Comment #82
claudiu.cristeaSo, it's green. However, it still Needs work because...
Comment::access()is really fixing the issue. We need to test that. Tagging with Needs tests.Comment #83
jonathanshawDo you plan to work on this? If not, a comment explaining would be really useful as its meaning is not clear to me.
Comment #84
claudiu.cristea@jonathanshaw, yes, I plan to work on this in the next days.
This issue blocks #2786587: [PP-1] Add thread depth configuration to comments.
Comment #85
pfrenssenIs looking good! Some small things that popped up during review:
There is a potential fatal error here, since
$this->first()can returnNULL.This documentation can be improved, how about:
Comment #87
claudiu.cristeaWorking on it
Comment #88
claudiu.cristeaFixing the context when creating comments, either as first-tier comments or as replies. This still needs testing.
Comment #89
claudiu.cristeaForgot the patch.
Comment #90
claudiu.cristeaWell... The problem now is that different create access results should be statically cached separately in
EntityAccessControlHandler::$accessCache. But it's easy to see, looking atEntityAccessControlHandler::createAccess(), that every time the create access result will get the same cid. There is already #2886800: EntityAccessControlHandler::createAccess() returns false positive cache hits because it ignores context dealing with this issue and I've posted a patch there. Unfortunately, this is postponed on #2886800: EntityAccessControlHandler::createAccess() returns false positive cache hits because it ignores context.Comment #91
claudiu.cristeaUnassigning.
Comment #92
claudiu.cristeaThis patch adds tests for #89.
Just to advance here, for testing purposed, I've created a patch on top of #2886800: EntityAccessControlHandler::createAccess() returns false positive cache hits because it ignores context. Partially moving to Needs review to see the bot.
Comment #93
claudiu.cristeaRemoving out-of-scope changes in CommentTestBase
Comment #94
claudiu.cristeaOn #70.2:
@larowlan
Well, not quite. The new method is using the comment field to retrieve the latest comment. But the field only records the latest published comment, see
CommentStatistics::update(). That said, the implementation is conceptually correct. In this patch I've only improved a little bit the method, fix method and var naming and, I think, the function should return neutral if there are no published comments. To prove this, I've added a regression test.Remaning tasks:
Comment #95
claudiu.cristeaChanges:
CommentController. I wonder if we cannot drop\Drupal\Tests\comment\Functional\CommentAccessTest.\Drupal\comment\CommentFieldItemList::get(), and if occurs then is something wrong in other place.CommentAccessControlHandler::buildCreateAccessCid(), so that every time we get a consistent$cid.::lastPublishedCommentAccess()should, indeed, return "allowed" when there's no comment. In some circumstances we still want to show0 comments.Still to be done:
CommentFieldItemList::access()receiveseditas$operation.Comment #96
claudiu.cristeaIn #95, I've reverted the return of
::lastPublishedCommentAccess()but I forgot to adapt the test.Comment #97
jonathanshawGreat to see this coming along. #28-38 has some discussion around 'administer comments'.
Comment #98
claudiu.cristea@jonathanshaw, thank you for pointing me to those comments. Reading them again and reading the code, I see some value in refactoring that too. At least, I can imagine that such a change could allow to implement custom access logic on the comment field when editing a node. A custom access handler could decide, for instance, that a user can close commenting on
articlenodes, while they cannot onpagenodes. And so on...However, looking to code occurrences, I think this will make this patch un-reviewable, it's already approaching 50KB. For this practical reason, I've considered @a_grizzli's suggestion from #35 and deferred this task to #3180177: [PP-2] Replaces usages of 'administer comments' permission w/ access handler checks follow-up.
Fixes:
comment_node_update_index(), which, according to #18.3.B3 will not be fixed here.Remaining tasks:
Comment #99
larowlanThis is looking great, the only things I could find here were nits
nit: this should be 'a composition', not 'as'. 'Decision about' should be 'The decision'
'a single permission on the last comment'
'the single' on 'the comment entity'
let's store this in a variable to avoid two container calls
the thread?
could we not just use $this->assertEquals($expectation, $this->controller...) instead
not is > is not
Thanks so much for moving this along.
For context, most of this existing logic around permissions was the same in D7, so it never really got to use the handler concept like other entity-types.
Glad to see this coming together
Comment #100
darvanenAssigning to clear nits.
Comment #101
darvanen#99.6: I agree. I double checked that using assertTrue or assertFalse doesn't provide better messaging in the test results and it does not. I reckon assertEquals($expectation, ...) is more readable.
Comment #102
claudiu.cristeaI came with a trivial idea in #2886800-9: EntityAccessControlHandler::createAccess() returns false positive cache hits because it ignores context to fix the problem of entity access control handler internal cache. But that issue is receiving now some inputs that probably will end in a more complex solution. As the solution that I've proposed in #2886800 - #21 is a so simple and with a minimal footprint I'll move it here in order to unblock this issue. Then, #2886800, could be relieved from the pressure of being a blocker here and could be improved accordingly. Inserting here that code snippet doesn't affect in any way the outcome of the other issue.
Comment #103
pfrenssenI'm OK with integrating the simple fix from #2886800: EntityAccessControlHandler::createAccess() returns false positive cache hits because it ignores context in here as proposed in #2879087-102: Use comment access handler instead of hardcoding permissions . It unblocks this issue in a non-disruptive way, and this is introducing the same protected method as is being worked on right now in the other issue. It will be trivial to adapt either this issue or the other, no matter which will be completed first.
Question: wouldn'tAccessResult::neutral()be more correct if access doesn't matter?edit: I checked this and it would forbid access.
The original documentation was better IMO, how about "And the user has access to the host entity."?
This test indicates that the functionality doesn't work as expected. It is checking that "view only" access is NOT permitted, but uses
assertTrue()which seems to prove that the access IS permitted.Comment #104
claudiu.cristeaFixed #103.2.
Ref. #103.1 & 3, I've reflected more on the fallback return value of
CommentFieldItemList::lastPublishedCommentAccess(). Actually, @pfrenssen is right in #103.1, there's no doubt that, when there are no comments, we should return neutral, which reads as make no opinion. That said, I've fixedcomment_node_search_result()accordingly, because, even with no published comments, when a field has OPEN status, it should print the comment count.Comment #106
claudiu.cristeaMoved the
$openflag block code but not in the right place.Comment #107
pfrenssenThanks for addressing the remarks! I did not connect the dots in my previous comment that the access check being inverted in the test was related to my other remark about the neutral access result :)
This is looking good to me now.
Comment #109
matroskeenRandom test failure, moving back to RTBC.
Comment #111
alexpottGiven we're using non-standard operations I think we need to document them somewhere and what they all mean. In a list. Here's as good a place as any. Or maybe in a new documentation section in comment.api.php - yeah there because it is easier for a developer to find.
The call to $field->access() can only return TRUE if there are comments - AFAICS. So this can be removed. Or we can add this to the previous line and make it first in the if - perhaps it is more performant.
I'm not convinced that the early returns and not merging in the results of the previous access checks here correctly maintains meta-data on the returned access result. For example if the PID is checked the access result does not have $entity added as a cacheable dependency. That does not feel right.
createis an interesting choice of operation to mean "post comments". I'm undecided if it is correct. Pairing it with field access is interesting. On one hand it is ties up nicely with the "create comment" permission and "create" operation for the comment entity. On the other it's not quite the same...viewvsview onlyis unfortunate.Comment #112
claudiu.cristea@alexpott, fixed #111.1, 2, 3.
About operation naming: There are two operations,
viewandeditthat are coming from Drupal core and we cannot change. I can see the problem withview, butviewis checked inEntityViewDisplay::buildMultiple()and I don't see how we can avoid that. On comments this is special as comment field on view display has also a form. I've documented all these aspects incomment.api.php. Regardingview only(#111.5), again, I agree. But how the name the view part of the "view"?Regarding the
createop (#111.4), I have no idea. Open to any suggestion.Comment #113
jonathanshawHaving read the docs you've just written on this, it seems that basically
view == view_only && create.If that's right, I think "view" and "view_comments" would be clearer names for the comments field operations. It sort of makes sense that 'view' would mean view the field including the form, and 'view_comments' would be a narrower aspect of the generic 'view'.
Alternatively 'read' or 'read_comments' would make some sense instead of 'view_only', as you can view a form but you can't really read a form.
Comment #114
claudiu.cristeaI've fixed all the remarks from #111.
@jonathanshaw, @alexpott, @larowlan and me, we had a chat on Slack and decided to rename the
view onlyoperation toview comments only.This is now ready for RTBC.
Comment #115
jonathanshawComment #116
catchCouple of nits and proper question on the MR.
Comment #117
claudiu.cristeaSetting back to RTBC as I only fixed the docs typos and a straight rename.
Comment #118
catchSlightly concerned about the "reply to $pid" access operation - is that absolutely necessary to do here or could it be moved to a follow-up?
Comment #119
claudiu.cristeaI've applied only a small docs fix.
Comment #120
jonathanshaw@claudiu.cristea do you have a thought about @catch's actual question:
"is that absolutely necessary to do here or could it be moved to a follow-up?"
Comment #121
catch'reply to' makes sense to me with comment entity access. i.e. <? $parent_comment->access('reply', $account) ?>
So could the comment controller do an && of field access 'create' and parent comment 'reply', or something along those lines.
Comment #122
claudiu.cristeaIf i'm getting right what you’re saying, you suggest to replace in controller:
with
There are 2 issues with this: There's no 'reply' operation on entity level. We can add it but how can we decide if a comment is "reply-able"? The second, and most important, is that, in field access method, we ask the comment access handler to decide if the new comment can be created. But, with this change, we'll not pass the parent ID as context to the handler. It's not enough to check, upstream, the
replyaccess against parent. The parent may contain other valuable data (fields) that are critical to make the decision if a reply can be created.EDIT: So, all the
reply to {$pid}stuff is to provide valuable context toCommentAccessControlHandler::createAccess()and subsequently tohook_comment_create_access().Comment #123
catchAnother possible way to do it would be that when there's a parent comment, delegate everything to $parent_comment->('reply') - and in that method, call the field access 'create' (which in core would be the only thing we check). That would mean that customising the behaviour would happen at the entity access level.
IMO it's OK to add an extra entity access operation.
I do think it would be easier to split this bit out to a follow-up, but if we need to address it here would prefer to be absolutely sure there's not an alternative.
Comment #124
claudiu.cristeaBut inside $parent_comment->access('reply') we don't have the field context. How to call the field access?
EDIT: We can probably get the field item list instance from the parent comment entity, via
::getCommentedEntity()and::getFieldName()Comment #125
catchYeah that information is all available. Moving to NW let's see what that looks like.
Comment #126
claudiu.cristea@catch, @jonathanshaw, Re. #123: I think, apart of adding
replyas entity access new operation, this one should defer the logic toCommentAccessControlHandler::checkAccess(), which now will have to also address a newreplyoperation. The reason is that I want to have some consistency and honour the entity access check architecture, which defers toEntityAccessControlHandlerInterfacehandler the logic. This will allow alsohook_comment_access()to receivereplyas operation.Comment #127
duneblI haven't read all this thread, thus maybe my comment is not very useful but here I go:
I end up here because I could not understand why
NodeAccessControlHandlerwas never called when I am trying to display or add a comment.More specifically, If I have a comment field attached to a Node type, I would expect that the
checkFieldAccess()method ofNodeAccessControlHandlerwill be called (ex: when a comment is displayed) like for any other field attached to the node.Why such an exception?
Comment #130
pfrenssenI have rebased this for 9.4.x.
In order to preserve the work done previously I have:
2879087-comment-access-handler-9.2.x.2879087-comment-access-handler-9.4.xto perform the rebaseSo the current "working branch" remains the same as before:
2879087-comment-access-handler. This is also the only one which has an associated merge request.I'm uploading patches for older D9 versions because the dynamically generated patches will now only apply on 9.4.x.
Comment #134
dan_metille commentedIs the patch still needed with Drupal 10?
As it is mentioned as 'required' by the https://www.drupal.org/project/group_comment module, it would be great to have some update.
Thanks for any feedback.
Comment #135
jaapjan commentedAttached a patch for 10.1. Patch is based on 01ed69c2. Slightly adjusted for D10.
Comment #136
jaapjan commentedIt seems I introduced a type error in the last patch when the account is not set. Updated patch. (Only addition
$account = $account ?: \Drupal::currentUser();in CommentFieldItemList.php).Comment #137
keszthelyi commentedThis patch is identical to #136, only I removed one change introduced by this commit. I'm not sure if that permission check is needed (as view access on the parent comment is checked), and feels like it goes against the idea of the patch itself (to use access checks instead of permission checks). We use this patch together with Group Comment module, and that check would only allow replying to comments if we'd use the 'View comments' global permission, and we'd like to avoid that in favor of group permissions.
Comment #138
Anonymous (not verified) commentedTested for version 10.3
#136 and #137 didn't apply...
Comment #139
loze commentedthis is 137 rerolled for 10.3.x
Comment #140
loze commentedComment #141
loze commentedMy patch in #139 was missing a use statement, this should work for 10.3.x
Comment #143
liam morlandI have put the patch in #141 into the merge request. The person who opened the merge request, @claudiu.cristea, will need to update the merge request so that it targets branch 11.x.
Comment #144
claudiu.cristeaDone
Comment #145
claudiu.cristeaBut why did you squash? We lost the commit history :(
Comment #146
dpi@claudiu.cristea if you want to rebuild you can try looking at the fork, and looking at the tags with `previous-` prefix.
Comment #148
herved commentedUpdate: I recreated the history from previous/2879087-comment-access-handler/2022-01-31 branch
https://git.drupalcode.org/issue/drupal-2879087/-/tags/previous%2F287908...
Merged 11.x, fixed conflicts, phpcs, phpstan and the tests touched by the patch.
5 tests are failing at the moment, among those these are concerning to me and relate to the changes in
CommentController::replyFormAccess:I believe this commit is wrong, we want to deny access if the parent access is not explicitely returning
AccessResult::allowed. Such as when the user doesn't have the "access comments" permission, we getAccessResult::neutraland we should deny access to reply.Reverting it fixed the above tests, I also added back null check protection after loading the parent comment which got lost.
But it caused CommentLinksTest.php:185 to fail.
This revealed an issue in that class/test as it doesn't actually create the comment type. Because of this, calling
$comment->getCommentedEntityatCommentAccessControlHandler.php:35was retrieving a user entity and denying access to view the reply link! fun one :)This relates to #3165822: [PP-1] Creating comment field w/o a comment type is allowed
----
There are 3 remaining tests failing, 2 about cache tags and 1 performance? (quite cryptic):
Also, #137: The branch was force pushed before so not sure what happened with that commit... but I don't see it in the current PR.
Comment #149
herved commentedFor now here are patches of the current MR23 commit f2ac23e6.
Comment #150
herved commentedImportant fix for cacheability:
The
CommentDefaultFormatterCacheTagsTestwas failing because theCommentDefaultFormatterwas not applying cacheability to the render array.---
On that note, we have a need in our project where the create comment permission will have a very high cache variance (per user).
This means that currently, it would kill the Dynamic Page Cache when
CommentDefaultFormatterchecks for the create permission and adds the lazy_builder and cacheability to the render array.I propose to move the access check inside the lazy_builder callback, in
CommentLazyBuilders::renderForm.I'd like to keep the scope to a minimum but this touches on the same lines and lots of projects may be in a similar scenario so we should try to preserve the page cache as much as possible. Viewing comment is less likely to have such requirements.
The change is very small, but this could also be reverted and a separate issue or follow up instead if anyone objects.
Comment #151
herved commentedI pushed an attempt now to address #118 to #125 (reply to {$pid}).
By calling
$parent_comment->access('reply')inCommentController::replyFormAccessand doing the actual check inCommentAccessControlHandler::checkAccessAlso, I had a look at the other issue #2786587: [PP-1] Add thread depth configuration to comments which requires this issue. It seems the approach there was to rely only on the create operation in
CommentAccessControlHandler::checkCreateAccessto limit the thread depth. Since we now have the reply access operation, I'm not even sure we need all the changes to $context (parent_comment, commented_entity).I'll sync with @claudiu.cristea on Monday.
---
PS: still the same 2 (less concerning) test failures which I left out for now since I don't quite understand them yet.
Comment #152
herved commentedComment #153
herved commentedComment #161
smustgrave commentedIssue summary needs to be flushed out.
1. This is adding a new trait but not sure I see that mentioned.
2. All the changes outside the comment module, to me seem out of scope.
3. Adding a test EntityCreateAccessCustomStaticCidTest outside of comment not sure I see the why.
Hiding all branches.
Comment #162
prudloff commentedComment #163
claudiu.cristeaHide patches
Comment #165
claudiu.cristeaUpdating IS
Comment #166
claudiu.cristeaRe #161:
1. Updated the IS. Mentioned the trait.
2. There are only 2 changes outside the comment module. Both are test expectations changes because of the comment module changes.
3. Removed EntityCreateAccessCustomStaticCidTest because in the meantime, core added a similar test.
Ready for review
Comment #167
loze commentedI’ve been following this issue and testing the patches for a while. Overall, things are working well, but I did notice a couple of cases where the behavior isn’t what I would expect:
1. When a comment is denied access by returning
AccessResult::forbidden()in ahook_comment_access()implementation, it is still visible in the thread.CommentDefaultFormatter::viewElements()doesn’t currently filter out inaccessible comments. It just callsviewMultiple(), which doesn’t perform access checks.I think
viewElementsneeds to do something along the lines of2. The comment field is hidden when the last comment is inaccessible.
CommentFieldItemList::lastPublishedCommentAccess()is used byCommentFieldItemList::access()to determine whether the entire field/thread is accessible. If the last comment in a thread is denied access, the entire field, including the comment form, is hidden. In this case,CommentDefaultFormatter::viewElements()is never called.Im not sure what the rationale for the
lastPublishedCommentAccess()method is here, couldn't we just check$account->hasPermission('access comments')here?Comment #168
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 #169
claudiu.cristeaOK, I've addressed the remarks from @larowlan (after #168 comment)
Comment #170
stefan lehmannI tried to create a (11.3.x) patch from the latest contents of the MR but I couldn't figure out what the actual commit / branch of core those changes are actually written against. So all these changes are missing .. sorry.
My patch only includes the patch from #149 re-rolled against 11.3.x.
Comment #171
jonathanshaw#167.1 I think should be a seperate issue, it's a pre-existing matter.
#167.2 touches upon something discussed in earlier MR comments, it's clear that the implemention here has the potential to be surprising and probably there's room to better document or cross-reference the explanations around this. However, I don't think this concern is strong enough to justify blocking the issue - docs can always be improved.
This has had a lot of eyes over a long period of time, so I suggest RTBC.
Given how esoteric the access considerations are, it's hard to be certain of BC here. This may be one of those potentially disruptive issues where it's good to commit to a branch a long time before a release.
Comment #174
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 #175
claudiu.cristeaSince #171 there are only straight re-rolls.
Comment #177
saidatomComment #178
saidatomComment #183
claudiu.cristeaWe need to evaluate latest changes. Not sure all make sense. However, I still see value at least in changes to CommentAccessControlhandler::buildCreateAccessCid()