There are basically two problems with the 8.5.8 and 8.6.2 security changes to Content Moderation:

  1. They introduce a new method to an existing interface. There is at least 1 contributed module that I know of that has an implementation of this interface -- Workflow Participants -- and another one that has an issue to include it -- Group. Any site that attempts to take the security update while one of these is enabled will encounter immediate, site-wide fatal errors due to the missing method implementation.
  2. They don't provide sufficient information for existing use cases. Both of those contributed modules require the context of the actual content entity in order to determine if a transition is valid, but the new method that was introduced doesn't offer that information. As a result, neither of these modules can even implement the new method properly.

Possible solutions

  1. Remove the new method from the interface. There is already a getValidTransitions() method on the interface. The constraint validator can simply get all the valid transitions and then check if the new state is among them.
  2. Adjust the method arguments from "Workflow/Current state/New state/User" to just "Entity/New state/User". (Both the workflow and the current state can be derived from the entity.) Adjust the implementation of Content Moderation's implementation accordingly.
  3. Introduce the content entity as an optional 5th argument to the method, making note that it will end up being required in a future version of Drupal.

Based on discussion, option #3 is the route that best complies with Drupal policies around API changes, so that is what has been chosen.

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

kevin.dutra created an issue. See original summary.

kevin.dutra’s picture

Issue summary: View changes
kevin.dutra’s picture

Status: Active » Needs review
FileSize
3.54 KB

Here's an implementation of the first proposed solution.

Status: Needs review » Needs work

The last submitted patch, 3: release-breaks-moderation-3007716-3.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

larowlan’s picture

We debated the API break and opted for a BC break to ensure there wasn't a silent security issue

I'd be in favour of option 2.

+++ b/core/modules/content_moderation/src/Plugin/Validation/Constraint/ModerationStateConstraintValidator.php
@@ -115,7 +115,9 @@ public function validate($value, Constraint $constraint) {
+      $valid_transitions = $this->stateTransitionValidation->getValidTransitions($entity, $this->currentUser);
+      if (!in_array($transition->id(), array_keys($valid_transitions))) {

+++ b/core/modules/content_moderation/src/StateTransitionValidation.php
@@ -49,12 +47,4 @@ public function getValidTransitions(ContentEntityInterface $entity, AccountInter
-    return $user->hasPermission('use ' . $workflow->id() . ' transition ' . $transition->id());

we lost the permissions check

kevin.dutra’s picture

Status: Needs work » Needs review
FileSize
11.42 KB
8.5 KB

I'm not sure I follow. getValidTransitions() finds all the transitions from the current state of the entity and filters out any that the user doesn't have access to with the permission check.

There is, apparently, an issue with the way it determines the current state when translations are involved, which should be why the test failures occurred, so here's a revised patch that should take care of that problem.

Sam152’s picture

One of the suggestions in the private issue was to remove the additional method. Here was an analysis of that which helps explain some of the motivations and problems with the approach you suggested:

I spent a long time playing with this and tinkering with the approach to remove the extra method. There is a fundamental issue with using getValidTransitions(ContentEntityInterface $entity...: for brand new entities that are unsaved, there is no original entity to load that represents the initial state of content for the purposes of validation.

That is, no matter what happens in all scenarios, we always assume something new starts from draft and then validate around that. For a brand new entity, we'd probably do something like: this entity is new, so the original entity is actually the same as the new one basically. But without cloning that entity and manually resetting the moderation state back to draft, we don't have a viable way of using the existing method to perform validations.

None of the new or existing tests caught this and since the validator uses $original_state which has it's own $original_entity->isNew() checks built in, even the violation messages were making complete sense while being incorrect.

I've uploaded this attempt. We could add further complexity by resetting $original_entity back to having $original_state when the entity is new and unsaved, but I don't like this approach much.

My preference would be use #27 and consider deprecating getValidTransitions in a follow-up in the public issue queue. It currently has 6 quite non-trivial usages, which I think would be much easier to divide and concur with a test-bot handy. I also think it's worth some public discussion to decide if we need to deprecate it at all: both the methods seem to have some utility in different contexts, but I'm not sold either way yet.

I haven't looked into those contrib modules, but perhaps entity could be an additional optional argument for advanced use cases?

Edit: Reviewing your patch in more detail, not sure if it gets around these issues, looks like you've come up with an approach for this.

Status: Needs review » Needs work

The last submitted patch, 7: interdiff_FAILED_ATTEMPT-29.patch, failed testing. View results

Sam152’s picture

+++ b/core/modules/content_moderation/src/StateTransitionValidation.php
@@ -42,19 +40,11 @@ public function __construct(ModerationInformationInterface $moderation_info) {
-    $current_state = $entity->moderation_state->value ? $workflow->getTypePlugin()->getState($entity->moderation_state->value) : $workflow->getTypePlugin()->getInitialState($entity);
+    $current_state = $this->moderationInfo->getOriginalOrInitialState($entity);

This has the problem that it's a change implementations would have to make, that would fail silently otherwise. So while the current BC break is annoying, it has forced the issue to the surface.

I think now that the existing fix is in stable releases, we unfortunately might have to do the following:

but perhaps entity could be an additional optional argument for advanced use cases

We could surmise that the method may have been called outside the context of having an entity available (despite the chances of that being pretty slim) and I don't think removing a method from an interface or changing an existing methods signature is an option anymore.

kevin.dutra’s picture

Definitely a bummer, but I totally understand. Here's the optional argument approach. (No interdiff since it's a totally different approach.)

Sam152’s picture

Thanks for sticking with this issue, really appreciate you helping to minimise disruption of this change.

+++ b/core/modules/content_moderation/src/StateTransitionValidationInterface.php
@@ -36,10 +36,12 @@ public function getValidTransitions(ContentEntityInterface $entity, AccountInter
+   * @param \Drupal\Core\Entity\ContentEntityInterface $entity
+   *   The entity to be transitioned.

I think we need to add the (Optional) tag to the comment here and possibly describe why it's optional, that is: the default implementation in core does not rely on this, some implementations do rely on this and that the entity param should be provided whenever possible by users of the API to ensure compatibility with contributed projects.

I wonder if we should actually deprecate NOT passing the entity in and do a trigger_error when it is not provided, that way we respect the existing BC, inform users $entity should be passed and then also be able to make it a required part of the interface for D9. Thoughts? In addition to that, lets add a change record.

Let me know if you have capacity to continue working on this, otherwise I'll see if I can put some time towards this.

kevin.dutra’s picture

I like the idea of deprecating the optional aspect. I think that'll help bring some visibility and help prevent potential issues. I'm a little tight for time this week, so if you end up having some bandwidth, please feel free. Thanks for your help!

Sam152’s picture

Okay great, adding the patch.

jhedstrom’s picture

+++ b/core/modules/content_moderation/src/StateTransitionValidationInterface.php
@@ -36,10 +36,12 @@ public function getValidTransitions(ContentEntityInterface $entity, AccountInter
+   * @param \Drupal\Core\Entity\ContentEntityInterface $entity
+   *   (optional) The entity to be transitioned.

Should this docblock note that the optional part of this is deprecated as well? Alternatively, since does it need to mention it's optional at all?

Sam152’s picture

Good point, I don't mind either of those options.

Sam152’s picture

Fixing comment.

Status: Needs review » Needs work

The last submitted patch, 16: 2915383-48.patch, failed testing. View results

Sam152’s picture

Status: Needs work » Needs review
FileSize
884 bytes
7.98 KB

Whoops, correct patch this time.

jhedstrom’s picture

Patch in #18 looks RTBC to me. Anything left to be done here? It would be great to get this in so #3007906: Core security release breaks workflow participants can get committed.

Sam152’s picture

Nothing else to do here as far as I'm aware.

jhedstrom’s picture

Status: Needs review » Reviewed & tested by the community

Then let's get this in :)

alexpott’s picture

Status: Reviewed & tested by the community » Needs work
Issue tags: +Needs issue summary update

The issue summary is out-of-date with the chosen solution. Given the complexity and how it was arrived at I think it is important that the issue summary is correct. Especially as the chosen solution doesn't appear to be amongst the possible solutions.

alexpott’s picture

Issue tags: +Needs change record

We also should have a change record here.

kevin.dutra’s picture

Assigned: Unassigned » kevin.dutra
Issue summary: View changes
Issue tags: -Needs issue summary update

Updating issue summary. I'll work on a CR.

kevin.dutra’s picture

Assigned: kevin.dutra » Unassigned
Status: Needs work » Needs review
Issue tags: -Needs change record

Added a draft change record: https://www.drupal.org/node/3026087

If someone can take a quick glance at the IS update and the CR, then this can probably go back to RTBC.

Sam152’s picture

I think the IS slightly mischaracterises the other options. This isn't exactly true:

Remove the new method from the interface ... no API change and no compatibility issues.

Other than that, looks good to me.

Sam152’s picture

Category: Bug report » Task
Sam152’s picture

Issue summary: View changes
Status: Needs review » Reviewed & tested by the community

Slightly tweaking the IS and RTBCing the CR here: https://www.drupal.org/node/3026087. I think I'm fine to RTBC given no changes were requested with the actual patch.

alexpott’s picture

Committed 73d9d9b and pushed to 8.7.x. Thanks!

Only committing to 8.7.x just to get a second opinion on backport because there is a change record and interface addition. I think this is eligible because it is not a break and critical.

Leaving at rtbc against 8.6.x

diff --git a/core/modules/content_moderation/tests/src/Unit/StateTransitionValidationTest.php b/core/modules/content_moderation/tests/src/Unit/StateTransitionValidationTest.php
index e9d97e49cc..541ff8f64d 100644
--- a/core/modules/content_moderation/tests/src/Unit/StateTransitionValidationTest.php
+++ b/core/modules/content_moderation/tests/src/Unit/StateTransitionValidationTest.php
@@ -52,7 +52,6 @@ protected function setUp() {
       ->addTransition('publish', 'publish', ['needs_review', 'published'], 'published');
   }
 
-
   /**
    * Verifies user-aware transition validation.
    *

Fixed coding standard on commit.

  • alexpott committed 73d9d9b on 8.7.x
    Issue #3007716 by Sam152, kevin.dutra, jhedstrom, larowlan: Security...
alexpott’s picture

Status: Reviewed & tested by the community » Fixed

@catch +1'd the backport in slack. Committed 82a677a32e and pushed to 8.6.x. Thanks!

  • alexpott committed 82a677a on 8.6.x
    Issue #3007716 by Sam152, kevin.dutra, jhedstrom, larowlan: Security...

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.