Problem/Motivation

Following #3545474: Security: Set Instance access handler where InstanceAccessControlHandler checks

  • if the user has the permission to use the current profile
  • if the user has the permission to update the current display

The first checks (about the current profile) is not enough, we need to also check each ApiController endpoint according to the island activated in the profile.

Proposed resolution

We don't do checks for "safe" endpoints:

  • display_builder.api_get

We do checks for unsafe endpoints:

  • display_builder.api_root_attach available only if BuilderPanel or LayersPanel island
  • display_builder.api_slot_attach available only if BuilderPanel or LayersPanel island
  • display_builder.api_update available only if BuilderPanel or LayersPanel island is activated
  • display_builder.api_paste available only if Menu island is activated
  • display_builder.api_duplicate available only if Menu island is activated
  • display_builder.api_delete available only if MenuDelete island is activated
  • display_builder.api_save_preset available only if MenuPreset island is activated
  • display_builder.api_third_party_settings_update only if UiSkinsPanel, UiStylesPanel or VisibilityConditionsPanel. Do we need to be more precise and check each panel differently?
  • display_builder.api_undo available only if HistoryButtons island is activated
  • display_builder.api_redo available only if HistoryButtons island is activated
  • display_builder.api_save available only if StateButtons island is activated
  • display_builder.api_restore available only if StateButtons island is activated
  • display_builder.api_revert available only if StateButtons island is activated
  • display_builder.api_clear available only if HistoryButtons island is activated with clear button activated
  • display_builder.api_sse available only if Collaboration island is activated

Do we need to add a method to IslandInterface? in order to get the list of endpoints allowed by each island.

Then, we can loop on activated islands and build the union of endpoints allowed by all activated islands.

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

pdureau created an issue. See original summary.

mogtofu33’s picture

Being able to call an api endpoints when you have the main permission, even if the profile do not expose the island in the UX is not a security problem, just a restriction of functionality problem.
Because of complexity introduced and load we should only be concerned by 'view' and 'save'.

Access to view/edit to the parent entity concerned is enough to decide the view and save instance. (We have no delete the parent entity, that's good).
Save button permission check should be done on the related save function for each entities at the time of saving.

Save preset as well could be check and the save part.

pdureau’s picture

Assigned: Unassigned » mogtofu33

Access to view/edit to the parent entity concerned is enough to decide the view and save instance. (We have no delete the parent entity, that's good).

So #3545474: Security: Set Instance access handler will be enough?

pdureau’s picture

Assigned: mogtofu33 » Unassigned
Issue tags: -display_builder-1.0.0-beta +display_builder-1.0.0-beta1

Checked with @mogtofu33

We will control only the actions related to StateButton:

  • display_builder.api_save available only if StateButtons island is activated
  • display_builder.api_restore available only if StateButtons island is activated
  • display_builder.api_revert available only if StateButtons island is activated

And SavePreset with 2 level checks:

  • MenuPreset island is available only if the user has "Administer pattern preset" role
  • display_builder.api_save_preset available only if MenuPreset island is activated

Moved to beta1 because its is small.

Don't forget to update the doc :)

mogtofu33’s picture

Title: Security: avaibility of endpoints according to islands » Security: availability of endpoints according to islands
pdureau’s picture

Assigned: Unassigned » pdureau
pdureau’s picture

Moved to post beta1 considering the last changes in #3545474: Security: Set Instance access handler and the other planned follow-ups.

pdureau’s picture

Assigned: pdureau » Unassigned
mogtofu33’s picture

mogtofu33’s picture

pdureau’s picture

  • display_builder.api_save available only if StateButtons island is activated
  • display_builder.api_restore available only if StateButtons island is activated
  • display_builder.api_revert available only if StateButtons island is activated

In #3616033: Reduce ProfileViewBuilder options, we will remove the State buttons [label, icon, icon+label, hidden] option, but it doesn't necessarily address this specific check (is the island activated).

mogtofu33’s picture

andres alvarez made their first commit to this issue’s fork.

pdureau’s picture

@andres alvarez if you work on it, don't hesitate to assign the ticket to you

andres alvarez’s picture

Assigned: Unassigned » andres alvarez

andres alvarez’s picture

Implemented in !407:

New IslandAccessCheck service gates the publishing endpoints (publish, restore, revert) to the state island, and save_as_preset to the menu_preset island plus the administer pattern preset permission.

Previously these routes only checked _entity_access: display_builder_instance.update, so users could hit the HTMX endpoints directly even when the corresponding island was disabled in the profile.

Added 6 kernel tests covering allowed, forbidden, missing profile, and missing permission.

PHPUnit green, PHPStan L8 clean, PHPCS clean. MR to follow.

andres alvarez’s picture

Status: Active » Needs review
andres alvarez’s picture

Assigned: andres alvarez » Unassigned
mogtofu33’s picture

Status: Needs review » Needs work

Thanks @andres-alvarez, could you fix the style error detected in the ci please.

andres alvarez’s picture

Status: Needs work » Needs review
andres alvarez’s picture

Follow-up coding standards fixes for IslandAccessCheck:

  • new static → new self in the final class factory method.
  • Added braces around the interpolated $island_id in the forbidden message string.

Both flagged by the CI pipeline. No functional change. MR to follow.

mogtofu33’s picture

Assigned: Unassigned » mogtofu33
mogtofu33’s picture

Thanks andres alvarez, I kept your commits and reworked the approach on top of them.

Instead of a _custom_access method per route, one access checker now answers a _display_builder_island: 'state' route requirement, so any island, contrib ones included, can guard its own routes the same way.

The permission check moved to MenuPreset::isAvailable(), so the menu item and the route follow the same rule, as decided in #4.

The profile and preset admin permissions are now declared by the main module, so the check also works without Display Builder UI.

pdureau’s picture

Assigned: mogtofu33 » Unassigned

Someone wants to review during Jean holidays?

smovs’s picture

Assigned: Unassigned » smovs

I will take a look

smovs’s picture

Assigned: smovs » Unassigned

Hi team!

Reviewed MR !407. The approach works for me: a _display_builder_island route requirement, with the permission rule kept in MenuPreset::isAvailable(). Two things to fix before RTBC:

  1. Moving the permissions can delete roles on existing sites.

    On sites installed before this MR, a role granted administer pattern preset or administer display builder profile still stores

    dependencies.module:
      display_builder_ui

    in its config. After this MR those permissions come from display_builder. When display_builder_ui is uninstalled,
    Role::onDependencyRemoval() finds nothing to remove and returns FALSE, so ConfigManager deletes the role. This affects any role that holds these permissions but not view display builder instance

    Suggestion: add a post update that re-saves the affected roles, plus a test that keeps such a role through an uninstall of display_builder_ui

  2. IslandAccessCheck passes NULL to addCacheableDependency() when the instance has no profile.

    testAccessWithoutProfile triggers: "Calling addCacheableDependency() with an object that doesn't implement CacheableDependencyInterface is deprecated in drupal:11.2.0 and is required in drupal:12.0.0" (#3232020). Suggestion: return early.

pdureau’s picture

Status: Needs review » Needs work

Thanks for the review. So we need to do some work.

andres alvarez’s picture

Assigned: Unassigned » andres alvarez
andres alvarez’s picture

Addressed @smovs's review (comment from Oct 29):

  1. Roles depending on display_builder_ui: added display_builder_post_update_recalculate_admin_permission_role_dependencies(). It re-saves the roles holding administer display builder profile or administer pattern preset, so core recalculates their dependencies and uninstalling display_builder_ui can no longer delete them. Covered by AdminPermissionRoleUpdateTest, with a control case for a role without those permissions.
  2. NULL deprecation: IslandAccessCheck::access() now returns neutral() early when the instance has no profile, with the instance as cache dependency and cachePerPermissions(). addCacheableDependency() no longer receives NULL. testAccessWithoutProfile asserts it.
  3. Documentation: docs/security-model.md already describes the _display_builder_island requirement, so no change was needed. I can add a sentence about the neutral result when there is no profile if you want it.
andres alvarez’s picture

Assigned: andres alvarez » Unassigned
Status: Needs work » Needs review
pdureau’s picture

Assigned: Unassigned » pdureau

We need a rebase before any review. I will do it.