Drupal Version
9.2.2
Domain module version
(8.x) 1.x-dev
Expected Behavior
[What did you try to do? What URL did you use to do it?]
Get the active domain from DomainNegotiator::getActiveDomain()
Actual Behavior
[What actually happened?]
DomainNegotiator::getActiveDomain() returns null in some circumstances, causing unexpected behaviors like 403 in domain front pages when combined with domain_site_settings & domain_access (see #3126141: Node access restricted for Anonymous), null pointer exceptions, etc.
It seems to be more frequent when called from event subscribers. Actually some existing implementations call getActiveDomain twice as a workaround (- 1 -) (- 2 -).
Steps to reproduce
[A bullet list of steps to reproduce the error. Note if the error always happens or sometimes happens.]
Unfortunately it is not clear what are the exact circumstances that makes DomainNegotiator::getActiveDomain() return null. It might be related to the state of cache or some other arbitrary condition. Commit 68ecdbf4 seems to be related.
Proposed solution
Ensure that DomainNegotiator::getActiveDomain() tries to look up the active domain when is is called for the first time, avoiding to do so by domain negotiator consumers as:
...
$active = \Drupal::service('domain.negotiator')->getActiveDomain();
if (empty($active)) {
$active = \Drupal::service('domain.negotiator')->getActiveDomain(TRUE);
}
...
Review the method documentation, there is no mention that it could return NULL, e.g., when no domain records defined.
| Comment | File | Size | Author |
|---|---|---|---|
| #18 | patch.txt | 801 bytes | mieg |
| #17 | 3226427-17.patch | 660 bytes | Ajeet Tiwari |
| #14 | domain-prevent_null_active_domain-3226427-14.patch | 3.68 KB | tim-diels |
| #8 | interdiff-3226427-2-8.txt | 430 bytes | manuel.adan |
| #8 | domain-prevent_null_active_domain-3226427-8.patch | 4.89 KB | manuel.adan |
Issue fork domain-3226427
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
manuel.adanComment #4
JeremyFrench commentedTrying a retest as the fails did not seem wholly related to the issue.
Also noting that I had issues with this in an access check hook, but calling `->getActiveId()` seemed to work.
Comment #5
JeremyFrench commentedThis seems to cause a segfault on CR when I apply the patch.
Comment #6
JeremyFrench commentedI've spent a while looking into the segfault.
What I believe happens is that as part of resolving the domain a dummy domain can be created.
Unfortunately in the process of creating a domain at least
domain_alias_domain_loadandDrupal\domain_language\LanguageDefault::get()are called, both of these callgetActiveDomain()and we end up in a loopIt looks like these both assume that getActiveDomain can return null if not resolved and will handle it. This means that changing the behaviour is more of an API change than anticipated.
I'm not quite sure of the best approach, from here on in. It feels like there is some inconsistency around this. I note that there is some code to handle recursive call errors in
LanguageDefault::get()so I am wondering if the dummy domain creation is causing more issues than we would expect. It doesn't seem obvious that resolving a domain would cause the creation of an entity. NB, I messed with this and it seems to trigger the recursive loop even if you load an entity before the resolver is complete.One approach could be to have a method to explicitly check if the domain is negotiated rather than inferring this from the behaviour of
getActiveDomainI've tested this locally and it seems to work, but it is an API change to what has been done previously. As one of the fixes which is needed is in domain_language I don't think we can do this.This leaves the option of a new method (or attribute to getActiveDomain) which will explicitly load the domain if it is not loaded. Is
getOrLoadActiveDomain()acceptable?Comment #7
manuel.adanRelated issues on other modules from the domain ecosystem.
Comment #8
manuel.adanSetting the negotiated flag before the actual domain negotiation prevents from infinite looping.
Comment #10
JeremyFrench commentedThis seems to work well now. I'm not sure about the remaining test fail but the code looks OK and It seems to have fix some long-standing issues I have seen with the domain resolver.
Comment #11
agentrickardI still need to see a reliable test for this case. It's likely happening at some very early stage of the bootstrap process.
What we need to know is what other modules or conditions trigger this error.
Comment #12
codebymikey commentedRe: #11
Ran into this bug with the
patemodule enabled, and occurs when$node->access('create', $account);is called as part of the access checks (and before Domain's event subscriber is called and Drupal's had a chance to cache the node create access permission check).domain_access_node_create_access()doesn't have the sameNULLcheck behaviour as indomain_access_node_access().See
68ecdbf4eebc8127476bd5154082e919bf0c7029commit.Comment #13
agentrickardThanks!
Comment #14
tim-dielsRe-rolled patch as the previous one could not be applied, and can not do an intertdiff.
The patch should focus on this issue and not fix other stuff. Will make it harder to review and get it applied.
Still needs tests.
Comment #15
agentrickardTests which seem unrelated may be failing because the patch changes the method signature of negotiateActiveDomain(), which should return the value of the current domain.
This return is a convenience for the caller.
Comment #17
Ajeet Tiwari commentedPlease review.
Comment #18
mieg commentedI actually had to do the same in domain_access_node_create_access after (some) editors started complaining.
Comment #19
mably commentedCould we have this rebased on the 2.0.x-dev branch please?
Comment #20
idebr commentedComment #23
dieterholvoet commentedI rebased against 2.0.x and I fixed another case I encountered when the domain could not be negotiated.
Comment #24
mably commentedThanks a lot! Looks like the MR needs to be rebase and some CI warnings and errors fixed.
Comment #25
mably commentedStill thinking that we are fixing the symptoms here and not the root causes of the problem...
Thinking out loud:
We are probably trying to implement a fix to avoid looping/reentrancy.
But doing so, once a piece of code has started negotiating the active domain, until negotiation is finished, all subsequent calls creating loops, will receive a NULL active domain. Looks like there is no way to avoid that, so all domain related modules should handle that properly.
Looks like we should probably replace
negotiatedbynegotiatingto fix that reentrancy/looop problem.And add another boolean to simply indicate that no domain is available at all and that we should stop trying to negotiate one. Don't see the case when a domain would suddenly become available later on in the request (except when we are creating domains, as is done in some tests).
To be continued...
Comment #26
mably commentedPushed a few fixes to MR 146. Could anyone review it, please? Thanks.
Comment #27
dieterholvoet commentedMR !146 seems to be the same as MR !147 now, which means that there aren't any changes since you last asked me to test.
Comment #28
mably commentedLet's wait a few days before merging.
Comment #30
mably commented