www.hubleto.eu Reddit User Guide Stack Overflow LinkedIn Report an issue

Permissions and access control

Hubleto decides in three layers whether a user may see or change something:

  1. Access to the app and to the controller – may the user open the page at all?
  2. Permissions of the roles – may the user's role do the operation?
  3. Access to the record – may the user see this record?

1. Access to a controller

Every controller has a $permission string. It is the full name of the controller class with slashes, e.g. Hubleto/App/Community/Deals/Controllers/Deals. The Renderer checks it before the controller is rendered:

src/Renderer.php (excerpt)
      if ($controllerObject->requiresAuthenticatedUser) {
        if (!$authProvider->isUserInSession()) {
          $this->logger()->info("User not authenticated => SignIn.");
          $controllerObject = $this->getController(SignIn::class);
        }
      }

      try {

        $permissionManager->setPermission($controllerObject->permission);

        if (
          $controllerObject->requiresAuthenticatedUser
          && !$controllerObject->permittedForAllUsers
        ) {
          $permissionManager->checkPermission();

The two flags of the controller change the result:

Property Default Meaning
$requiresAuthenticatedUser true The user must be signed in. Set false for public pages.
$permittedForAllUsers false Any signed-in user may open the controller. ApiController sets it to true.

Permission flags of a controller.

PermissionsManager::granted() then grants the access in this order:

  1. The user is an administrator (administrator type or administrator role).
  2. The app is marked permittedForAllUsers.
  3. The permission starts with Hubleto/App and the user has the app enabled in their profile (the list of apps in the user's record). The match is by the namespace prefix, so enabling Hubleto\App\Community\Deals grants all controllers of the app.
  4. The user's role has the permission (table permissions of the Settings app), or the permission is granted to all roles.
apps/Settings/PermissionsManager.php (excerpt)
  public function grantedForRole(string $permission, int|string $userRole): bool
  {
    if (empty($permission)) return TRUE;

    $granted = (bool) in_array($permission, (array) ($this->permissionsData[$userRole] ?? []));

    if (!$granted) {
    }

    return $granted;
  }

NOTE In practice an app is either enabled for a user or not. To enable an app for a user, an administrator selects it in Settings → Users. To grant access to a single controller, assign its permission string to a role.

Default permissions of an app

In install round 3 the app creates its permissions:

Override assignPermissionsToRoles() when a specific role needs a specific controller:

public function assignPermissionsToRoles(): void
{
  parent::assignPermissionsToRoles();

  /** @var \Hubleto\Framework\Models\RolePermission $mRolePermission */
  $mRolePermission = $this->permissionsManager()->createRolePermissionModel();

  $mRolePermission->grantPermissionByString(
    $idRole,
    'Hubleto/App/Custom/CarRental/Controllers/Cars'
  );
}

2. Permissions of the table and its operations

The description of a table contains the permissions canCreate, canRead, canUpdate and canDelete. The React table and form read them and hide the Add, Delete and Save controls (and do not load the data without canRead). Model::describeTable() sets all four to true. The framework contains a commented-out variant that would ask the permission manager for <Model>:Read, :Create, :Update and :Delete, so today these permissions are not derived from the roles. Change them in describeTable(). EmailMarketing turns the table into a read-only report when the user groups the data:

apps/EmailMarketing/Models/Recipient.php (excerpt)
    if (isset($filters['fGroupBy'])) {
      $description->setPermissions(false, false, false, false);

Description\Table::setPermissions($canCreate, $canRead, $canUpdate, $canDelete) changes only the parameters that are not null.

NOTE In the current code Table::setPermissions() tests $canCreate also when it sets canRead, so setPermissions(canRead: false) does not work as expected when canCreate is null. Pass all four values, or set $description->permissions['canRead'] directly, until this is fixed in the framework.

3. Access to a record: ownership columns

The Hubleto\Erp\RecordManager filters every read query by the ownership columns of the table. The columns are normal columns of your model; there is no special configuration:

Column Effect
id_owner The owner of the record sees it.
id_manager The manager of the record sees it.
id_team Members of the team see it.
shared_with Users and teams the record is explicitly shared with.

Ownership columns.

Add id_owner to a model when users should see only their records. Set its default to the current user, as Customer and Deal do:

apps/Deals/Models/Deal.php (excerpt)
      'id_owner' => (new Lookup($this, $this->translate('Owner'), User::class))->setReactComponent('InputUserSelect')->setDefaultValue($this->authProvider()->getUserId()),

Ownership is applied in prepareReadQuery(), which is used by tables, forms, lookups and boards. That is why you must always call parent::prepareReadQuery() and extend its result in your own record managers. A query that you build yourself with $model->record->... is not filtered, so use prepareReadQuery() when you read records on behalf of a user.

apps/Customers/Models/RecordManagers/Customer.php (excerpt)
  public function prepareReadQuery(mixed $query = null, int $level = 0, array|null $includeRelations = null): mixed
  {
    $query = parent::prepareReadQuery($query, $level, $includeRelations);

    // Virtual tag count
    $query->selectSub(function($sub) {
      $sub->from('cross_customer_tags')
        ->join('customer_tags', 'customer_tags.id', '=', 'cross_customer_tags.id_tag')
        ->whereColumn('cross_customer_tags.id_customer', 'customers.id')
        ->selectRaw("COUNT(DISTINCT customer_tags.id)");
    }, 'tags_count');

    return $query;
  }

Checking permissions in your code

// the signed-in user
$idUser = $this->authProvider()->getUserId();
$roles  = $this->authProvider()->getUserRoles();

// does the user have a permission?
if ($this->permissionsManager()->granted('Hubleto/App/Community/Deals/Controllers/Deals')) {
  // ...
}

// is the app enabled for the active user? (used by the desktop to build the sidebar)
$this->permissionsManager()->isAppPermittedForActiveUser($app);

Related pages