Problem/Motivation
"Page redirect" Rabbit Hole action performs a redirect to the entered destination URL or token, which is converted to URL during the action execution. There is no validation before converting a string into Drupal\Core\Url object, which leads to the exception.
Steps to reproduce
It can be reproduced by entering a token that will return an empty string or incorrect URL value.
Initial steps:
You can reproduce this by:
Checking the "Allow these settings to be overridden for individual entities" box on a Content Type Edit page.
Then edit a node of that type.
On the Rabbit Hole Settings tab of the node edit page, select Page Redirect and leave the Redirect Path empty.
Save the node and load the node logged out, or as a user without the admin bypass permission.
Have a sad at the error above.
Proposed resolution
Following the discussion, it was agreed that the best solution would be providing a fallback action, that will be executed if the URL destination is not correct.
Remaining tasks
Verify and commit the latest patch.
User interface changes
There is a new field "Fallback behavior" available when "Page Redirect" action is selected.
| Comment | File | Size | Author |
|---|---|---|---|
| #34 | 2926929-34.patch | 21.49 KB | matroskeen |
| #32 | 2926929-interdiff-30_32.txt | 14.91 KB | matroskeen |
| #32 | 2926929-32.patch | 20.48 KB | matroskeen |
Comments
Comment #2
dylan donkersgoed commentedPatch attached.
Comment #3
keshavv commentedComment #4
keshavv commentedComment #5
keshavv commentedComment #6
dylan donkersgoed commentedWhat did the last patch change? They appear to be identical.
Comment #7
borisson_Doesn't look like that patch changed anything. If it did - an interdiff would be a really helpful to show your work. Hiding that patch for now.
Comment #8
marvil07 commentedEven if the redirect is setup, it can use tokens, and the related tokens may be empty for any reason; e.g. using a field which is empty.
I would suggest to alternative or additionally check validity during redirection handling.
In specific, the mentioned exception is thrown from
Drupal\Core\Url::fromUserInput(), and I would suggest to handle that exception at the call time and do nothing, i.e. do not redirect.I avoided adding a log entry since this could potentially fill logs.
Adding a patch doing that to avoid the php fatal error.
Comment #9
dylan donkersgoed commentedThis:
is a good point and should be accounted for. However, I don't think just taking no action is the right way to go. If someone set a redirect they were likely expecting the user to not hit that page. I think throwing an access denied error would be preferable to potentially allowing a user to access a page they weren't meant to.
I'm attaching a patch that throws an access denied instead. I've also brought my changes (to make the field required in the first place) back in - I think those probably weren't left out intentionally?
Comment #10
marvil07 commented@Dylan Donkersgoed, thanks for the feedback and the new patch.
About #8, the idea was to focus on the main problem, without caring on UI; so it was provided as an alternative approach.
I agree that we should care on UI too, so thanks for trying to integrate the change!
Triggering access denied may not be the right thing to do in all cases, but it sounds like a good default.
Debugging the problem may be harder in that case, so I would suggest to at least log watchdog message about it.
Even better, I would suggest to instead make a new interaction point, e.g. a new module hook, to allow changing the behavior from a custom module if needed; and provide the default of logging a message about the problem and triggering access denied.
Moving to NW for at least logging a message, but even better if a new hook is also introduced.
Comment #11
etroid commentedI would agree that making an assumption of either access denied or access granted when the redirect path is empty might not work in all cases. We could expose the fallback option in the UI instead:
Fallback behavior:
- Page not found
- Display the page
- Access denied
Comment #12
etroid commentedHere's a first stab at making the fallback configurable. Update hook provided to supply new default config value.
Comment #13
mandclu commentedThe configurable fallback worked for me. I tried all three options and they appear to work as intended.
Comment #14
mandclu commentedAfter using this for a bit, I realized that some AJAX operations were failing on my site, especially related to IEF and entity browsers.
After some investigation, I found that the AJAX call was returning this error:
Declaring as static the validateRedirectSettingsForm method added by this page fixed the errors, and restored the AJAX functionality. I'll try to roll a new patch soon.
Comment #15
rafmagsou commentedThis patch implements a fix suggest by mandclu aboutn validation static method
and a tag support on validation it's related by issue #3061163: Allow using in redirect settings
Comment #16
jeroentComment #17
anybodyThank you all! Can we have an interdiff from #12 to #15?
Both look good to me so far. #15 even better, but I wonder why \Drupal::pathValidator()->isValid($redirect) from the referenced issue #3061163: Allow using <front> in redirect settings is not used?
Comment #18
rafmagsou commentedHello, it's not a particular reason but if this makes sense we can make an approach combine both of patches.
Comment #19
mmbkI think, that the validation of the url-field should not be part of this patch, because the '' route is handled by #3061163.
EDIT: #3060274 modifies the validator as well, so we will get some conflicts when both patches are applied.
Furthermore there should a default-default-fallback action. As long the default-fallback action is not configurated, the exception is still triggered. The savest action would be imho to deny the access.
Comment #20
mmbkI implemented the suggestions, I made in #19 and adjusted the codestyle errors injected from the previous patches in the files I touched.
Comment #21
mmbkRemarks to my patch
Each switch statement should have a default section, as the 'access denied' is default when entering the config-form. I used it here as well
Personally I don't like a case statement without a break, but I did not touch it, and there is no recommandation in the code-style :-(
Comment #22
mmbkI've seen it too late: When updating an existing installation with this patch, the new field `redirect_fallback_action` is not installed, so a update_hook is missing:
Comment #23
mmbkComment #24
mmbkComment #25
mmbkThis patch adds the new field to existing entities.
Comment #26
benjifisherI have been looking at this issue.
First, a minor point. I confirm that the patch in #4 is identical to the one in #2.
Second, most of the patches on this issue set the "Redirect path" field to required and also add a custom validator for that field. I guess the idea is that the field is set to required only when Behavior is set to "Page redirect"; this requires JavaScript; and we want to check the field even when JavaScript is disabled.
I tested this by removing the custom validator and then submitting the form with the "Redirect path" field empty. With JavaScript enabled, the form is not submitted, as expected, since the field is required. I did not test with JavaScript disabled, but I did test with the current version of the 8.x-1.x branch, and I get a validation error from
FormManglerService::validateFormRedirect(). That code was added in #2922902: Relative Redirects paths should be Absolute paths, after the initial patch on this issue. At this point, we can remove the custom validator from this patch.The attached patch removes the redundant validator. I am still working on this issue, so I am assigning it to myself.
Third, the patch in #25 adds an update function. I tested it, and it works as expected. However, I have also been working on the related issue #3060274: Tokens as redirect. I have git branches for that issue and this one. Git merges the two branches cleanly, but then the update function fails. So we will have to be careful when combining these two patches.
Fourth and last point: the patches on this issue combine two different approaches, and I think this issue should really be split into two. One approach is to make the field required. The other approach is to provide a fallback for when it is empty. (The common use case is when, after #3060274, the field is a token but the token evaluates to empty.) If we have the fallback, do we really need to make the field required?
Here is a possible use case, but perhaps there are better ones. I want a different redirect for every node of some content type. So I want to configure the content type to redirect, but leave the redirect path empty. Then I can enter a different redirect path on every node. (A better solution, after #3060274, is to use a token.)
Because of that last point, I am adding the "Needs issue summary update" tag.
Comment #27
benjifisherThe attached patch does two things:
usestatement.rabbit_hole.behavior_settingsconfig items.I should have done (1) as part of the patch in #26.
As for (2), I noticed that the settings form for an existing content type (already configured to use the Redirect option) did not have any fallback selected. The new update function takes care of that. It also handles the default settings. (There are two defaults: one for per-bundle settings and the other for per-entity settings.)
I considered combining this update function with the one from #25. That is an option, but I decided that there may be sites that have already applied that patch, and it would be convenient for those sites if I created a separate update function.
I do not have time to continue working on this issue (unless I run into problems with further testing) but I do have some suggestions.
Why not use the default value from config? I think that would be
rabbit_hole.behavior_settings.default_bundle.Instead of throwing these exceptions directly, I think it makes more sense to list all the available Rabbit Hole behavior plugins (or maybe just those that do not require configuration) and invoke the configured one here. That is, we should be calling the
performAction()method from theaccess_deniedorpage_not_foundplugin instead of throwing exceptions here.Same idea as (1). Let's get rid of this constant and use the configured default instead.
Comment #28
benjifisherIn #26, I mentioned a problem with the update function after merging the patches from this issue and #3060274: Tokens as redirect. Probably that was a mistake on my part, or maybe I somehow fixed it as part of the patch in #27.
I am attaching two new patches here: one is a combined patch, with both #27 from this issue and #23 from #3060274. The second is a version of the patch from #27 that can be applied after applying #23 from #3060274. The only differences between the second patch and the one in #27 are metadata lines and one line of context.
Comment #29
benjifisherThe patch from #3060274-23: Tokens as redirect was committed to the dev branch, so the "after" patch in #28 is now the active patch on this issue. Unfortunately, the patch needs a reroll after #3115059: Can't enter redirect urls longer than 128 characters, so here is an updated version. The only differences between this patch and the "after" patch are context lines.
Do we care whether
#statescomes before or after#maxlength?Comment #30
etroid commentedThanks for the patch @benjifisher. Added a few more lines to ensure the config value does not get wiped out on export.
diff --git a/src/Entity/BehaviorSettings.php b/src/Entity/BehaviorSettings.php
index 76d15fa..d774020 100644
--- a/src/Entity/BehaviorSettings.php
+++ b/src/Entity/BehaviorSettings.php
@@ -21,7 +21,8 @@ use Drupal\rabbit_hole\Exception\InvalidBehaviorSettingException;
* "action" = "action",
* "allow_override" = "allow_override",
* "redirect" = "redirect",
- * "redirect_code" = "redirect_code"
+ * "redirect_code" = "redirect_code",
+ * "redirect_fallback_action" = "redirect_fallback_action"
* },
* config_export = {
* "id",
@@ -29,7 +30,8 @@ use Drupal\rabbit_hole\Exception\InvalidBehaviorSettingException;
* "action",
* "allow_override",
* "redirect",
- * "redirect_code"
+ * "redirect_code",
+ * "redirect_fallback_action"
* },
* links = {}
* )
Comment #31
matroskeenI think it's time for me to take a look at this issue, so here we go :)
The last patches look good at a glance, but I'd like to apply some changes to make the code cleaner and easier to extend. I'm attaching is a re-roll against the latest dev version (we already have a bunch of new changes there) with some refactoring (I hope I didn't break it yet).
(This is a draft version, I'll try to get back to it this week)
I agree with @benjifisher that we should execute Rabbit Hole plugins instead of duplicating the job of the action inside the PageRedirect action; That's why the fallback action login was moved into BehaviorInvoker.php class.
Comment #32
matroskeenHere is a new version based on #30.
A list of applied changes:
\Drupal\rabbit_hole\Plugin\RabbitHoleBehaviorPlugin/PageRedirectto\Drupal\rabbit_hole\BehaviorInvoker;#27 1-3 are valid points, but it should be fixed in several places. I created a follow-up issue for that: #3180520: Use default values from default bundle configuration instead of hardcoded constants.
Comment #34
matroskeenRemoved some outdated asserts.
Comment #36
matroskeenI think it's good enough to be committed.
If anyone has any suggestions - feel free to open a new issue or re-open this one.
Thanks, @all!