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:
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?
- d
isplay_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.
Comments
Comment #2
mogtofu33 commentedBeing 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.
Comment #3
pdureau commentedSo #3545474: Security: Set Instance access handler will be enough?
Comment #4
pdureau commentedChecked with @mogtofu33
We will control only the actions related to StateButton:
display_builder.api_saveavailable only ifStateButtonsisland is activateddisplay_builder.api_restoreavailable only ifStateButtonsisland is activateddisplay_builder.api_revertavailable only ifStateButtonsisland is activatedAnd SavePreset with 2 level checks:
MenuPresetisland is available only if the user has "Administer pattern preset" roledisplay_builder.api_save_presetavailable only ifMenuPresetisland is activatedMoved to beta1 because its is small.
Don't forget to update the doc :)
Comment #5
mogtofu33 commentedComment #6
pdureau commentedComment #7
pdureau commentedMoved to post beta1 considering the last changes in #3545474: Security: Set Instance access handler and the other planned follow-ups.
Comment #8
pdureau commentedComment #9
mogtofu33 commentedComment #10
mogtofu33 commentedComment #11
pdureau commentedIn #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).
Comment #12
mogtofu33 commentedComment #14
pdureau commented@andres alvarez if you work on it, don't hesitate to assign the ticket to you
Comment #15
andres alvarez commentedComment #17
andres alvarez commentedImplemented in !407:
New
IslandAccessCheckservice gates the publishing endpoints (publish,restore,revert) to thestateisland, andsave_as_presetto themenu_presetisland plus theadminister pattern presetpermission.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.
Comment #18
andres alvarez commentedComment #19
andres alvarez commentedComment #20
mogtofu33 commentedThanks @andres-alvarez, could you fix the style error detected in the ci please.
Comment #21
andres alvarez commentedComment #22
andres alvarez commentedFollow-up coding standards fixes for
IslandAccessCheck:new static→new selfin the final class factory method.$island_idin the forbidden message string.Both flagged by the CI pipeline. No functional change. MR to follow.
Comment #23
mogtofu33 commentedComment #24
mogtofu33 commentedThanks andres alvarez, I kept your commits and reworked the approach on top of them.
Instead of a
_custom_accessmethod 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.
Comment #25
pdureau commentedSomeone wants to review during Jean holidays?
Comment #26
smovs commentedI will take a look
Comment #27
smovs commentedHi team!
Reviewed MR !407. The approach works for me: a
_display_builder_islandroute requirement, with the permission rule kept inMenuPreset::isAvailable(). Two things to fix before RTBC:On sites installed before this MR, a role granted
administer pattern presetoradminister display builder profilestill storesin its config. After this MR those permissions come from
display_builder. Whendisplay_builder_uiis uninstalled,Role::onDependencyRemoval()finds nothing to remove and returns FALSE, soConfigManagerdeletes the role. This affects any role that holds these permissions but notview display builder instanceSuggestion: add a post update that re-saves the affected roles, plus a test that keeps such a role through an uninstall of
display_builder_uiIslandAccessCheckpasses NULL toaddCacheableDependency()when the instance has no profile.testAccessWithoutProfiletriggers: "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.Comment #28
pdureau commentedThanks for the review. So we need to do some work.
Comment #29
andres alvarez commentedComment #30
andres alvarez commentedAddressed @smovs's review (comment from Oct 29):
display_builder_ui: addeddisplay_builder_post_update_recalculate_admin_permission_role_dependencies(). It re-saves the roles holdingadminister display builder profileoradminister pattern preset, so core recalculates their dependencies and uninstallingdisplay_builder_uican no longer delete them. Covered byAdminPermissionRoleUpdateTest, with a control case for a role without those permissions.IslandAccessCheck::access()now returnsneutral()early when the instance has no profile, with the instance as cache dependency andcachePerPermissions().addCacheableDependency()no longer receives NULL.testAccessWithoutProfileasserts it.docs/security-model.mdalready describes the_display_builder_islandrequirement, so no change was needed. I can add a sentence about the neutral result when there is no profile if you want it.Comment #31
andres alvarez commentedComment #32
pdureau commentedWe need a rebase before any review. I will do it.