Problem/Motivation

The Workspaces module needs to take every precaution possible to disallow any kind of information/content from leaking into the live site, so it provides and enforces a form validation handler which doesn't allow most Drupal forms to be submitted when a workspace is active.

However, other modules need an easy way to mark their forms as workspace-safe. In HEAD, this can be accomplished by setting $form_state->set('workspace_safe', TRUE); in a form, but this approach has various drawbacks:

- there are modules which provide many forms and the logic for determining whether they're workspace-safe can be complex and needs to be reused
- it requires module authors to be aware of this custom form state value

Proposed resolution

Standardize the way for modules to mark their forms as workspace-safe by using two helper interfaces:

\Drupal\Core\Form\WorkspaceSafeFormInterface
\Drupal\Core\Form\WorkspaceDynamicSafeFormInterface

Remaining tasks

Review.

User interface changes

Nope.

API changes

Two new interfaces are added in the \Drupal\Core\Form namespace.

Data model changes

Nope.

Release notes snippet

TBD.

Issue fork drupal-3208390

Command icon 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

amateescu created an issue. See original summary.

amateescu’s picture

Status: Active » Needs review
StatusFileSize
new8.1 KB

This should do it.

amateescu’s picture

StatusFileSize
new8.1 KB

Version: 9.2.x-dev » 9.3.x-dev

Drupal 9.2.0-alpha1 will be released the week of May 3, 2021, which means new developments and disruptive changes should now be targeted for the 9.3.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

fabianx’s picture

Status: Needs review » Reviewed & tested by the community

Overall looks great, and the event will work really well - I think - Code is really clean.

There are normally 3 cases, but one is not really needed:

- Form is safe to submit while in a workspace and in Live [workspace_safe = TRUE]
(mostly GET request forms -- maybe we could mark those automatically as safe as they must not mutate data per HTTP specification?)

- Form is not safe to submit while in a workspace, but safe to submit in live [workspace_safe == FALSE], e.g. config entities

- Form is safe to submit, while in a workspace, but not in Live => We don't need this case

==> The boolean is fine to use (and pre-existing anyway).

While a form alter is as centralized (because the event piggy-backs on the form alter), I can see that the event is making the logic cleaner.

I defer to core committers to decide if that's a warranted use-case for a new event.

Unrelated, but saw this here:

- We could consider a follow-up to automatically mark all GET request forms as safe. If someone violates HTTP spec, I don't think that is a problem of Core to protect them.

- Overall I think contrib will provide a way to mark all forms as safe -- if you have a special permission, but I am not sure if that's a core thing.

Because I can see the case of a developer that wants to develop views while in a workspace. [e.g. being really frustrated of that safeness feature]

Nice patch!

catch’s picture

Status: Reviewed & tested by the community » Needs work
Issue tags: +Needs change record, +Needs tests
+++ b/core/modules/workspaces/src/EventSubscriber/DefaultWorkspaceSafeForms.php
@@ -0,0 +1,42 @@
+    $form_object = $event->getFormState()->getFormObject();
+    $is_workspace_form = $form_object instanceof WorkspaceFormInterface;
+    $is_views_exposed_form = $form_object instanceof ViewsExposedForm;
+    $is_search_form = in_array($event->getFormId(), ['search_block_form', 'search_form'], TRUE);
+

Wouldn't search and views module be responsible for implementing this? Since it's core it doesn't really matter and workspaces could do it on their behalf, but more to the point there are no implementation examples outside of workspaces module itself.

Also needs a change record and tests.

I'm not keen on adding events to core, but that's a separate issue to the need for an API for this.

tim.plunkett’s picture

Even though it just means another two subscribers, I'm +1 to doing things "correctly" and having more examples of how it's done. One for search, one for views.

s_leu made their first commit to this issue’s fork.

tim.plunkett’s picture

MR is empty.

s_leu’s picture

I will provide patches instead of using an MR as discussed with amateescu because we need a packport to 8.x , so patches seem to be easier

s_leu’s picture

StatusFileSize
new17.72 KB
new11.51 KB

Here's first patch and interdiff adding tests as requested. I didn't add a dedicated test method to test workspaces internal forms as that's already being done within the added test methods when the workspace gets switched, as the switcher form is one of these module internal forms.

I can still add tests for more internal forms if that's desired. Also, I'll provide a backport of the patch for 8.x sometime soon, will set this to on needs review once I got the backport ready too.

s_leu’s picture

Status: Needs work » Needs review
StatusFileSize
new17.72 KB
new11.51 KB

Here's the patch for 8.9.x .

joachim’s picture

> - it scatters the knowledge about workspace integration across many files (forms), instead of a centralized place

From the point of view of the module that defines the form, that's a good thing though, isn't it? The form class defines the form, and having the form class also declare itself as workspace-safe keeps things about the form in the same place.

The current MR is requiring modules that define forms to add a lot of boilerplate, and an event seems like overkill for something that sounds like it's declarative.

I can see that having to do '$form_state->set()' means that you can't know whether a form is OK until you've actually created the form build array.

But what about adding a method to the form class? The one drawback I can see with that is if a hook_form_alter() changes the workspace-safety of a form. But with the event, the module doing the form alteration has to be pretty careful with event weights to make sure it fires after the form-defining module's event! To cover that case, we could keep the $form_state->set() for dynamic cases?

amateescu’s picture

Issue tags: -Needs tests

@joachim, we are keeping the $form_state->set() option in place. The new code just does $form_state->set('workspace_safe', $event->isSafe()); based on the event data.

Simple (one-off) forms still have the option to do it in the form class, the new event is more geared towards the cases where multiple forms have to be handled together, for example based on shared logic like checking the form class inheritance or more complex scenarios like we need to use for Layout Builder in #3000749-15: Layout builder overrides on a single content item not allowed in a workspace.

amateescu’s picture

Status: Needs review » Reviewed & tested by the community
StatusFileSize
new17.72 KB
new1.39 KB

Reviewed the changes from #11 and they look good to me. Here's a quick fix for the cspell errors.

amateescu’s picture

Issue tags: -Needs change record
amateescu’s picture

StatusFileSize
new17.72 KB
new637 bytes

Missed a spot :)

Version: 9.3.x-dev » 9.4.x-dev

Drupal 9.3.0-rc1 was released on November 26, 2021, which means new developments and disruptive changes should now be targeted for the 9.4.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

eclipsegc’s picture

Having looked over this patch, I have a couple thoughts about the EventSubscribing portions of this that I hope will bring us more in line with where Symfony is going, but I'm unsure of the current approach within core to this space.

  1. +++ b/core/modules/workspaces/src/FormOperations.php
    @@ -71,20 +82,9 @@ public function formAlter(array &$form, FormStateInterface $form_state, $form_id
    +    $this->dispatcher->dispatch($event, WorkspaceEvents::WORKSPACE_SAFE_FORM);
    

    Are we at a point where we can safely stop assigning names to the event and just use the event class convention in Symfony?

  2. +++ b/core/modules/workspaces/src/WorkspaceEvents.php
    @@ -0,0 +1,18 @@
    +<?php
    

    If we can stop naming events, we could remove this class. Even if we can't stop doing that yet, we could potentially use WorkspaceSafeFormEvent::class as the name. That would conform to the new standard, and allow us to just drop the second parameter when the time comes.

xjm’s picture

Status: Reviewed & tested by the community » Needs work

Not sure #19 is in scope; followup maybe?

I have no meaningful feedback on the API addition itself, but since this has been stuck in the RTBC queue for six months, here is your "committer guilt review". :)

  1. This is apparently missing some docs which is causing PHPCS fails. Not sure why the issue didn't get NWed; probably the bulk update.
     
  2. Was #7 addressed? Edit: Maybe it didn't need to be because the patch already does that? I think it is reasonable as a response to @catch's feedback in #6.
     
  3. There are at least a few places that could also have return typehints added, especially where it is pure API addition (rather than just subclassing). Lots of missing void return typehints in particular.
  4. +++ b/core/modules/workspaces/tests/src/Functional/WorkspaceSafeFormTest.php
    @@ -0,0 +1,147 @@
    +  protected static $modules = ['search', 'views', 'workspaces', 'node', 'block'];
    

    This is 81 chars and so should use multiline array syntax.

  5. +++ b/core/modules/workspaces/tests/src/Functional/WorkspaceSafeFormTest.php
    @@ -0,0 +1,147 @@
    +   * @param array $permissions
    +   *   Additional permissions to be granted to the created user.
    

    string[]?

  6. +++ b/core/modules/search/src/EventSubscriber/SearchWorkspaceSafeFormsSubscriber.php
    @@ -0,0 +1,38 @@
    +   * Marks search forms as workspace safe.
    

    Nit (here and elsewhere): "workspace-safe" should be hyphenated.

  7. +++ b/core/modules/workspaces/src/Event/WorkspaceSafeFormEvent.php
    @@ -0,0 +1,102 @@
    +   * Gets the form id.
    

    Nit: "ID" should be uppercase.

  8. +++ b/core/modules/workspaces/tests/src/Functional/WorkspaceSafeFormTest.php
    @@ -0,0 +1,147 @@
    +    $vultures = $this->createWorkspaceThroughUi('Vultures', 'vultures');
    +    $gravity = $this->createWorkspaceThroughUi('Gravity', 'gravity');
    +
    +    return [$test_node, $test_node2, $vultures, $gravity];
    

    Excellent test data nouns.

  9. +++ b/core/modules/workspaces/src/FormOperations.php
    @@ -24,14 +24,24 @@ class FormOperations implements ContainerInjectionInterface {
    -  public function __construct(WorkspaceManagerInterface $workspace_manager) {
    +  public function __construct(WorkspaceManagerInterface $workspace_manager, EventDispatcherInterface $dispatcher) {
         $this->workspaceManager = $workspace_manager;
    +    $this->dispatcher = $dispatcher;
       }
    

    Almost gave kneejerk feedback that this needed a deprecation for the new service, but that is not necessary since the class has a factory method to inject the services.

    Edit: Fixing bad code snippet. Dreditor and GitLab require opposite cursor-drag directions to highlight lines in the diff and apparently I'm starting to adapt to GitLab despite more than a decade of core patches. :P

NW for points 1-7. Thanks!

amateescu’s picture

Status: Needs work » Needs review
StatusFileSize
new17.93 KB
new5.03 KB

Thanks @xjm for reviewing! :)

#7 was addressed in #11, and I fixed the rest of #20 here.

I agree that #19 should be first established in a policy issue or something, then changed everywhere at once.

Version: 9.4.x-dev » 9.5.x-dev

Drupal 9.4.0-alpha1 was released on May 6, 2022, which means new developments and disruptive changes should now be targeted for the 9.5.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.5.x-dev » 10.1.x-dev

Drupal 9.5.0-beta2 and Drupal 10.0.0-beta2 were released on September 29, 2022, which means new developments and disruptive changes should now be targeted for the 10.1.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

s_leu’s picture

This is working fine and I guess it's time to set it to RTBC once someone else confirms. I guess it's also worth mentioning that there's some other approach to this topic in the workspaces extra module's issue over here: #3304460: Different handling of form submissions inside workspaces

xjm’s picture

@s_leu, the next step is indeed a code review of @amateescu's fixes.

smustgrave’s picture

Status: Needs review » Needs work
Issue tags: +Needs Review Queue Initiative, +Needs change record updates

Issue summary as a TBD for a release note, that probably still needs to happen.

Think the change record should include the new service
+ search.workspace_safe_forms:
+ class: Drupal\search\EventSubscriber\SearchWorkspaceSafeFormsSubscriber
+ tags:
+ - { name: event_subscriber }

getSubscribedEvents
Should be typehinted

Change record also doesn't mean new event/service for views ViewsWorkspaceSafeFormsSubscriber

Version: 10.1.x-dev » 11.x-dev

Drupal core is moving towards using a “main” branch. As an interim step, a new 11.x branch has been opened, as Drupal.org infrastructure cannot currently fully support a branch named main. New developments and disruptive changes should now be targeted for the 11.x branch, which currently accepts only minor-version allowed changes. For more information, see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

amateescu’s picture

Status: Needs work » Needs review
Issue tags: -Needs change record updates
StatusFileSize
new17.16 KB
new7.98 KB

Rerolled #21, addressed #19 and #26, and updated to the latest core standards.

@smustgrave those new services are event subscribers, so they're not APIs that need to be announced in the CR :)

Status: Needs review » Needs work

The last submitted patch, 28: 3208390-28.patch, failed testing. View results

amateescu’s picture

Status: Needs work » Needs review
StatusFileSize
new17.16 KB
new901 bytes

Fixed the test failure.

smustgrave’s picture

Status: Needs review » Needs work

Change record reads well but could it or the issue summary be updated that this change is also marking certain core blocks as workspace safe? The change makes sense 100% just feels like something that should be noted.

s_leu’s picture

StatusFileSize
new25.81 KB

Adding a patch that still applies on 10.1.x as the latest patch here doesn't any more.

amateescu changed the visibility of the branch 3208390-add-an-api to hidden.

amateescu changed the visibility of the branch 11.x to hidden.

amateescu’s picture

Priority: Normal » Major
Status: Needs work » Needs review
Issue tags: +Needs architectural review

Since @catch was not a fan of the event approach, here's an alternative that would allow modules to declare their forms as workspace-safe even when the workspaces module is not installed.

I added the trait in the Drupal\Core\Form as an initial proposal, but it could also be added somewhere in the system module I guess..

Once Workspaces will be marked as stable, more and more modules will have to integrate with it, so I'm raising the priority of this issue.

needs-review-queue-bot’s picture

Status: Needs review » Needs work

The Needs Review Queue Bot tested this issue.

While you are making the above changes, we recommend that you convert this patch to a merge request. Merge requests are preferred over patches. Be sure to hide the old patch files as well. (Converting an issue to a merge request without other contributions to the issue will not receive credit.)

amateescu’s picture

Status: Needs work » Needs review
Issue tags: +no-needs-review-bot
fabianx’s picture

Status: Needs review » Reviewed & tested by the community

RTBC

My only concern was for existing code that sets workspace_safe = TRUE or FALSE, but the $form_state property always takes precedence, so this is just a way to set the default state of the form.

And it's very nice to use.

amateescu’s picture

Hiding old patch files.

alexpott’s picture

Status: Reviewed & tested by the community » Needs review

Why a trait over an empty interface in the Drupal\Core\Form namespace? There no logic or possible dynamism with an interface (or is that an intended side effect... sometimes a form is workspace safe and sometimes it is not... that feels odd).

I think

$form_state->set('workspace_safe', $form_state->getFormObject() instanceof Drupal\Core\Form\WorkspaceSafeInterface);

is nicer than messing with a trait and possible dynamic safeness. Anything that wants to be dynamic can still manipulate form state and set workspace_safe as they see fit.

amateescu’s picture

@alexpott the idea with a trait came up specifically for the dynamic nature that some modules might need, like Layout Builder in core :)

The difference is that in HEAD, LB has to call a method in each form class for marking its forms as workspace-safe, while with the trait approach it only has to ensure that its method name matches the one from the "top-level" trait, and Workspaces takes care of calling it in its own form alter.

I don't mind going for the interface approach you suggested, Workspaces itself does that currently and it works great when there's no dynamism involved. Should we wait or ask for other opinions or do you feel (strongly or not) that the interface will be far simpler to implement for the majority use cases?

alexpott’s picture

Status: Needs review » Needs work

Sorry I completely missed that we already have the dynamic use-case in core - so the trait implementing a method works out nicely there I agree.

What's not so nice is

    if (method_exists($form_state->getFormObject(), 'isWorkspaceSafeForm')) {
      $workspace_safe = $form_state->getFormObject()->isWorkspaceSafeForm($form, $form_state);
    }
    else {
      // No forms are safe to submit in a workspace by default.
      $workspace_safe = FALSE;
    }

we're making assumptions about the signature of isWorkspaceSafeForm that we can't really. So... I think we should add an interface WorkspaceSafeInterface that declares one method - isWorkSpaceSafeForm() and have the two trait be implementations of the interface for people to use.

    if ($form_state->getFormObject() instanceof WorkspaceSafeInterface)) {
      $workspace_safe = $form_state->getFormObject()->isWorkspaceSafeForm($form, $form_state);
    }
    else {
      // No forms are safe to submit in a workspace by default.
      $workspace_safe = FALSE;
    }

Then at least we are programming to interfaces and people can come up with their own implementations safe in the knowledge that they are implementing an interface.

Yes adding an interface and a trait is not as nice as just adding a trait - and maybe PHP will solve that someday but I think it is a price worth paying in order to avoid calling a method incorrectly and ding type-safe programming.

amateescu’s picture

Issue summary: View changes
Status: Needs work » Needs review

Changed the approach to use two interfaces instead of a trait, and updated the issue summary and the change record.

fabianx’s picture

Status: Needs review » Reviewed & tested by the community
Issue tags: -Needs architectural review

Back to RTBC!

The new approach is fully BC compatible for layout_builder and makes it much easier for forms to mark themselves as safe by default / definition.

  • catch committed 9ad5e45d on 10.3.x
    Issue #3208390 by amateescu, s_leu, Fabianx, tim.plunkett, xjm,...

  • catch committed 51f471d8 on 11.x
    Issue #3208390 by amateescu, s_leu, Fabianx, tim.plunkett, xjm,...
catch’s picture

Version: 11.x-dev » 10.3.x-dev
Status: Reviewed & tested by the community » Fixed

This looks great now, glad I pushed back on the new event, much easier to see what's going on everywhere.

Committed/pushed to 11.x and cherry-picked to 10.3.x, thanks!

Status: Fixed » Closed (fixed)

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