Overview

XB UI needs to be able to check permissions and alter the UI accordingly. We need some basic utilities that will allow this to be handled consistently throughout the app.

There are 3 types of checks:

Config entities

See #3516648: Update `ExperienceBuilderController` to pass current user's XB high-level permissions to the client

drupalSettings.permissions => [
            'globalRegions' => bool,
            'sections' => bool,
            'codeComponents' => bool,
            'contentTemplates' => bool,
],

Creating new content entities

TBD, #3529895: Provide the client with `create` operation access information similar to #3516657.
Still to be defined, but potentially something like:

drupalSettings.permissions => [
     "contentEntityCreateOperations' => {
             "entity_type": {
                   "bundle": label/FALSE,
              }
              "xb_page" :  {
                    "xb_page": "Page" // The singular label for "New Page"
              }
              "node": {
                   "article": "Article",  // The singular label for "New Article"
                   "blogpost": FALSE,
               }
      ],
],

Existing content entities

See #3516657: Update `ApiContentControllers::list()` to expose available content entity operations in `meta` and \Drupal\experience_builder\XbUriDefinitions[link])

Calling /xb/api/v0/content/{entity_type} for the list of entities, returns a set of allowed links for each content entity:

'entity_id': {
    'id': 1,
    'title': "Page 1",
    "status" => TRUE,
    "path" => '/page-1',
     ...,
     "links": {
            "edit-form": "/xb/xb_page/1",
            "delete-form':  "/xb/xb_page/1",
            "https://drupal.org/project/experience_builder#link-rel-duplicate": "/xb/api/v0/content/xb_page"
            "https://drupal.org/project/experience_builder#link-rel-set-as-homepage":  "/xb/api/v0/content/xb_page"
     }
}
...

Proposed resolution

Communicate permissions

Provide a list of permissions to the FE within drupalSettings.xb.permissions or similar.

type Permissions = string[];
interface DrupalSettings {
  xb: {
     ...
     permissions: Permissions;
  }
}

Utility functions

Create utility functions to help manage and check permissions:

  • Check if a user has an individual permission
    const hasPermission = (permission: string, userPermissions: Permissions): boolean => {
      return userPermissions.includes(permission);
    };
    
  • Check if a user has multiple permissions
    const hasPermissions = (requiredPermissions: Permissions, userPermissions: Permissions): boolean => {
      return requiredPermissions.every(permission => userPermissions.includes(permission));
    };
    
  • Check if a user has one or more of a given list of permissions
    const hasAnyPermission = (requiredPermissions: Permissions, userPermissions: Permissions): boolean => {
      return requiredPermissions.some(permission => userPermissions.includes(permission));
    };
    

React Component

A reusable React component that can be used to conditionally render UI elements based on permissions:

import React, { ReactNode } from 'react';

interface PermissionCheckProps {
  hasPermission?: string;
  hasAnyPermission?: Permissions;
  hasPermissions?: Permissions;
  denied?: ReactNode;
  children: ReactNode;
  userPermissions: Permissions;
}

const PermissionCheck: React.FC<PermissionCheckProps> = ({
  hasPermission,
  hasAnyPermission,
  hasPermissions,
  denied = <div>You don’t have permission</div>,
  children,
  userPermissions
}) => {
  const isAllowed = 
    (hasPermission && hasPermission(userPermissions)) ||
    (hasAnyPermission && hasAnyPermission(hasAnyPermission, userPermissions)) ||
    (hasPermissions && hasPermissions(hasPermissions, userPermissions));

  return <>{isAllowed ? children : denied}</>;
};

export default PermissionCheck;

Example of how it would be used:

<PermissionCheck hasPermission="canAdd" denied={(<button disabled title="You do not have permission to add">Add</button>)}>
  <button>Add</button>
</PermissionCheck>

Important Note

Backend Enforcement: Always enforce permissions on the backend as frontend checks can be bypassed.

Out of scope

Real-time Updates: Real-time updates to permissions don't need to be supported; users must reload the page to get updated permissions.

User interface changes

Design Considerations:

We need to come up with a set of common rules/UX guidelines to cover the following and when they should be used

  • UI Element Visibility: Show or remove UI elements based on permissions.
  • UI Element State: Enable or disable UI (visuals for disabled states) elements based on permissions.
  • Custom UI: Display custom UI in place of the regular UI when a permission is missing.
  • Custom dialog: Display custom dialog when a user performs an action they don't have permission to do.
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

jessebaker created an issue. See original summary.

wim leers’s picture

As long as we agree on what the server will pass to the client in #3516648: Update `ExperienceBuilderController` to pass current user's XB high-level permissions to the client, work on this could begin in parallel. But that probably makes little sense?

mglaman’s picture

#3516648: Update `ExperienceBuilderController` to pass current user's XB high-level permissions to the client is finished, can the updated and agreed upon format be updated here? Are we using raw permissions from Drupal or should it be createPage, etc because that is not what was done in the other ticket.

penyaskito’s picture

@mglaman That includes the config-related permissions, which are all-or-nothing so are just flags. But we might want to still postpone on #3516657: Update `ApiContentControllers::list()` to expose available content entity operations in `meta` for the content related ones.

wim leers’s picture

Title: Create React Permission Utilities » Make the XB UI show only UI elements/operations available to the current user
Category: Feature request » Task
Priority: Normal » Major
Issue tags: -stable blocker +beta blocker

#4++

Given that this could otherwise result in information disclosure vulnerabilities (not access bypass because the server side pieces are taken care of, at least once #3494915: Support entity-level + field-level access checking in auto-save — i.e. in `experience_builder.api.api.(layout.post|auto-save.post)` + #3516432: Update all XB routes to respect content entity update/field edit access of edited XB field are done), going ahead and tagging this a blocker for #3515932: Milestone 1.0.0-beta1: Enable creation of non-throwaway sites.

penyaskito’s picture

penyaskito’s picture

Issue summary: View changes

Added the different scenarios of permissions that need to be exposed to the client UI.

penyaskito’s picture

Issue summary: View changes

just formatting

jessebaker’s picture

In the interests of keeping things as simple as possible on the FE, I was hoping to get something like the following

JSON:

{
  "drupalSettings": {
    "xb": {
      [...]
      "permissions": [
        "read global regions", "update global regions", "delete global regions", "read sections", "update sections", "delete sections", ...
      ],
    }
  }
}

Because then in the UI checking if we should display a button or not is as simple as, for example:

JSX:

<>
    {drupalSettings.xb.permissions.includes('delete sections') && (
        <button>Delete section</button>
    )}
</>

penyaskito’s picture

Assigned: Unassigned » jessebaker
Status: Active » Needs work

Amazing 😍
NW per @larowlan review and I think I spotted a wrong permission check, but this looks amazing already and covers more scope than I expected.

penyaskito’s picture

jessebaker’s picture

Assigned: jessebaker » Unassigned
Status: Needs work » Needs review

Finally got to the bottom of a number of issues that were causing a lot of friction in writing the Playwright tests for this work. All resolved now and ready for another round of review.

The summary on the MR is the most up to date place for all info on this work.

wim leers’s picture

Assigned: Unassigned » jessebaker
Issue summary: View changes
Status: Needs review » Needs work
StatusFileSize
new49.69 KB
new20.02 KB
new29.02 KB

Did thorough manual testing. No problems found.

Tip for fast manual testing: change

          'permissions' => [
            'globalRegions' => $this->currentUser->hasPermission(PageRegion::ADMIN_PERMISSION),
            'patterns' => $this->currentUser->hasPermission(Pattern::ADMIN_PERMISSION),
            'codeComponents' => $this->currentUser->hasPermission(JavaScriptComponent::ADMIN_PERMISSION),
            'contentTemplates' => $this->currentUser->hasPermission(ContentTemplate::ADMIN_PERMISSION),
            'publishChanges' => $this->currentUser->hasPermission(AutoSaveManager::PUBLISH_PERMISSION),
          ],
          'contentEntityCreateOperations' => $this->getContentEntityCreateOperations(),

to

          'permissions' => [
            'globalRegions' => $this->currentUser->hasPermission(PageRegion::ADMIN_PERMISSION),
            'patterns' => $this->currentUser->hasPermission(Pattern::ADMIN_PERMISSION),
            'codeComponents' => FALSE,
            'contentTemplates' => $this->currentUser->hasPermission(ContentTemplate::ADMIN_PERMISSION),
            'publishChanges' => $this->currentUser->hasPermission(AutoSaveManager::PUBLISH_PERMISSION),
          ],
          'contentEntityCreateOperations' => [],

etc. to test any combination of things.

Found two bugs, both in the same area:

👍 All operations available
🐛Delete operation available only
The <hr> should be omitted.
🐛 Zero operations available
The ellipsis operations button should not appear at all.
jessebaker’s picture

I'm not sure it's possible to actually get into those situations (yet?!) but good catch. At the moment, you can't even access XB if you don't have permission to edit XB pages so if you can access XB then you will always have at least one option in the menu.

Anyway I've added some more robustness around the UI when people have no permissions or can delete things but don't have any other items in the menu which should keep this looking tidy in the future no matter what configuration of permissions people end up with!

wim leers’s picture

Assigned: jessebaker » utkarsh_33
Status: Needs work » Reviewed & tested by the community

At the moment, you can't even access XB if you don't have permission to edit XB pages so if you can access XB then you will always have at least one option in the menu.

Is that true? What if I can create/edit articles?

In any case, it won't be true anymore very soon, because #3529924: Add access check for using Experience Builder at all: if >=1 content entity type with an XB field can be created or edited. will definitely allow you to load XB if you cannot use Pages at all, assuming you have an editable XB field on article nodes.

Thanks for adding that robustness — I won't test again, I bet you tested it thoroughly 😄👍

wim leers’s picture

Assigned: utkarsh_33 » Unassigned

/me typed "u", return, cmd+space, click "Save" … and didn't spot that it didn't select Unassigned 😅

  • wim leers committed 7fd7f19b on 0.x authored by jessebaker
    Issue #3516641 by jessebaker, wim leers, penyaskito, mglaman, larowlan:...
wim leers’s picture

Status: Reviewed & tested by the community » Fixed
Issue tags: -Needs issue summary update

🤩🤩🤩🤩 And … the XB UI actually starts to come fully alive! 🧟‍♂️

wim leers’s picture

Status: Fixed » Closed (fixed)

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