Problem/Motivation
At the moment it seems LinkFormatter doesn't prevent inaccessible links (e.g: links to unpublished nodes) from being displayed.
Steps to reproduce
- Create a link field in e.g: article nodes
- In "Manage display" use LinkFormatter or LinkSeparateFormatter
- Create an unpublished article node (U)
- Create a published article node (P) and link node U
- As anonymous, we see the link to node U which leads to a 403
Proposed resolution
Some sites may want to render those inaccessible links so I propose to add a setting (checkbox) in the formatter to run access checks.
If it is checked, and the user doesn't have access, the link won't be displayed.
Remaining tasks
- MR with tests
- Code review + manual testing
- Commit
User interface changes
Introduced terminology
API changes
- Constructor signature change
Data model changes
- New config schema key + formatter setting
Release notes snippet
| Comment | File | Size | Author |
|---|
Issue fork drupal-3231247
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
herved commentedFirst draft proposal on the approach.
Tests still todo.
Comment #4
herved commentedTests should pass with this, let's see.
New tests validating the added access_check still todo.
Is this approach decent? It is somewhat similar to what
EntityReferenceFormatterBasedoes.I thought about adding this in the field storage/instance setting but
\Drupal\link\Plugin\Field\FieldType\LinkItemdoesn't have such access check.Only
\Drupal\Core\Field\FieldItemListhas an access check but it applies to the whole item list which wouldn't be viable AFAICS.Another way I think would be to use
#access_callback, similar to what\Drupal\Core\Url::toRenderArraydoes, but then the link field (wrapper) would be rendered, even if all items are inaccessible. The only emptiness check happens in\Drupal\Core\Field\FormatterBase::view.Comment #5
larowlanThis should probably be a private issue, unpublishing
Comment #6
larowlanComment #7
herved commentedHello @larowlan, what does that mean?
Do you consider this as a security issue? (I don't think it is but... idk)
Thanks
Comment #8
avpadernoComment #13
herved commentedNote that I wrote this patch initially with the idea to make it non-disruptive.
If the goal is to apply access checks by default then we may need to reverse the logic.
Though I wonder, is there a reason why
\Drupal\Core\Url::toRenderArrayhas the access check, but\Drupal\Core\Url::toStringdoesn't?Couldn't we include this access check in
toStringby default? Perhaps via an "access" key set to TRUE by default in\Drupal\Core\Url::$optionswhich we can disable if needed?).Comment #14
herved commentedReroll for 10.1.x and adapted the unit tests.
Functional tests are still needed but it would be great to get some feedback on the whole issue/approach before moving forward with that.
As-is, the access check happens in a similar way to
EntityReferenceFormatterBasebut I wonder if this access check should even happen at this level, and in this way (or perhaps using a#pre_renderor#access_callback)...This could extend way beyond link formatters.
Comment #15
herved commentedFixing PHPStan issues.
Comment #16
gregglesrepublishing after discussion that this can be handled in public.
Comment #18
herved commentedMR created (PS: Nightwatch tests seem unstable, I had to re-run).
Please check #4 and #13, I feel like this issue extends beyond link formatters so leaving to needs work...
Here is a static patch from MR for composer.
Comment #19
herved commentedComment #20
herved commentedRebased and moving to review, to get feedback on the approach, thanks.
Comment #21
smustgrave commentedLeft some comments on MR.
Comment #22
herved commentedThanks, though I wasn't looking for a code review yet (tests and upgrade path still todo) but rather opinions on the overall approach / scope as it may extend way beyond link formatters.
See #13.
Comment #23
joachim commentedI am marking the following as duplicates:
- #2938807: Only display internal links to content the user has access to view -- older, but has no code
- #3368223: Link field > Access to internal links is not checked on display. -- more recent, has a MR
- #2968609: Link fields do not check access for entity refs -- older, has a MR, but the MR has not been updated in longer
Code from those issues with MRs should be compared with what is here, in case there are ideas that should be integrated into the MR here.
Participants in all those issues should have credit added here.
Comment #24
joachim commented#3368223: Link field > Access to internal links is not checked on display.'s MR is pretty basic.
#2968609: Link fields do not check access for entity refs's MR has a lot of stuff that isn't here.
1. More work on the URL, e.g.:
Should we be doing that here?
2. More stuff on caching that I've not looked at in depth
3. A functional test (though it could really be a kernel test)
Comment #25
joachim commentedAlso, outputting broken internal links really looks like a bug to me -- one of the duplicate issues filed it as such too.
Comment #26
joachim commentedI've tested the current MR with field data in all forms:
- a path alias to a node: /mynode
- a system path to a node: /node/1
- using the lookup to store the entity
In all cases, the item is skipped when the user doesn't have access, so that's working well!
I don't know about the caching side of things though -- see my comment above.
Comment #28
dcam commentedIn my opinion this isn't a bug. Wanting to link to a page that can give a 403 response, for instance prompting users to authenticate to view content behind a pay wall, is an equally valid use case. That's borne out by the fact that this is made optional with the formatter setting. It isn't only for the sake of backward compatibility. It's because the current behavior is a thing people would actually want to do.
Comment #29
herved commentedI agree with #28, displaying inaccessible URLs is a totally valid use case.
So indeed this is not a bug, and a formatter settings does make sense.
When I created this issue, I was confused why
\Drupal\Core\Url::toRenderArrayhad the access check, but\Drupal\Core\Url::toStringdidn't. But I see now that #3343153: Remove deprecated code from \Drupal\Core\Url removed::toRenderArray.I also agree with all review comments so if anyone wants to address those, please feel free. Or I will find some time later this week.
Cheers.
Comment #30
herved commentedAnswering to #24:
1. If I'm not mistaken, this looks like a crude attempt to check entity access.
AccessManager::checkNamedRouteI used here is a much cleaner alternative.2 & 3. The access cacheability is collected with the current MR here in the formatter, but we indeed still need a test here to validate the access is respected and the cacheability as well if possible.
I'll work on that next.
Comment #32
herved commentedApplied suggestions, attaching MR snapshot and also patch that applies to 11.3.
Comment #33
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 #35
herved commentedRebased on main, and addressed the review threads in the MR. Ready for another round of review.
Comment #36
marcoscanoI used AI to help reviewing this MR, whose output I filtered and reflected in comments in the MR with my own words. Also adjusting slightly the IS to be more complete to the default template.
Comment #37
herved commentedAll great suggestions, thank you @marcoscano. Applied, and I refactored the kernel test.
I did find a real blocker though: with
access_checkon, a link pointing at a deleted entity makes the page uncacheable, I added a test proving it. If you are already running this patch, this affects you.FWIW, here is Claude code's assessment:
So I don't think this can land as it stands: one dangling link anywhere on a page silently disables page caching, which is a worse trade than the broken links the setting removes.
Shall I create a new issue and postpone this one on it?
In light of this I will remove the patch from the site I maintain, where it was guarding exactly one inaccessible link after inspecting values in DB.
Comment #38
marcoscanoNice catch... thanks for the test covering and exposing it 👍
IMO it would be unfortunate to block this now and postpone it on a new core API addition (new interface, changes to `ParamConverterManager`, `AccessManager`, `EntityConverter`), etc... that would make this stall for quite a bit I believe.
Alternatively (and understanding it isn't the ideal fix), would it make sense to avoid it now with a pre-check in the formatter that avoids hitting the exception in the first place? For example something along the lines of:
then
findUnresolvableEntityType()would mirror the five linesAccessManager::checkNamedRoute()uses to build$parameters + $route->getDefaults(), and then for each entry in$route->getOption('parameters')would call$entity_converter->applies()and->convert(), returning the entity type definition of the first NULL.(I believe something similar could be done with
$storage->load($id)to check existence of the target, if we think it would be simpler)Maybe we can create the new core issue for the changes in the param converter, with a
@todointestDanglingReferenceIsCacheable()pointing at it, so this can be refactored when the new API is in place?--
AI Disclaimer: I have used an LLM to help analyze this issue, whose output I have reviewed and filtered.