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

User management and authentication

Users, roles and sign-in are implemented by the Auth app. Permissions are decided by the PermissionsManager of the Settings app. This page describes the pieces; the rules for checking permissions are in Permissions and access control.

Classes

Class Purpose
Hubleto\App\Community\Auth\AuthProvider Signs users in and out, keeps the user in the session, "remember me", password reset.
Hubleto\App\Community\Auth\Models\User The users.
Hubleto\App\Community\Auth\Models\UserRole The roles.
Hubleto\App\Community\Auth\Models\UserHasRole The assignment of roles to users.
Hubleto\App\Community\Auth\Models\UserHasToken Tokens for "remember me".
Hubleto\App\Community\Auth\Controllers\SignIn, ForgotPassword, ResetPassword, NotEnoughPermissions The public pages.

Classes of the Auth app.

User types and roles

The type describes what the user is. The role describes what the user may do. Both are integers and both have constants:

apps/Auth/Models/User.php (excerpt)
  public const int TYPE_NOT_SPECIFIED = 0;
  public const int TYPE_ADMINISTRATOR = 1;
  public const int TYPE_CHIEF_OFFICER = 2;
  public const int TYPE_MANAGER = 3;
  public const int TYPE_EMPLOYEE = 4;
  public const int TYPE_ASSISTANT = 5;
  public const int TYPE_EXTERNAL = 6;
apps/Auth/Models/UserRole.php (excerpt)
  public const ROLE_ADMINISTRATOR = 1;
  public const ROLE_CHIEF_OFFICER = 2;
  public const ROLE_MANAGER = 3;
  public const ROLE_EMPLOYEE = 4;
  public const ROLE_ASSISTANT = 5;
  public const ROLE_EXTERNAL = 6;

A user of type TYPE_ADMINISTRATOR or with the role ROLE_ADMINISTRATOR passes every permission check.

The user in the session

After the sign-in the user is kept in the session as an array. normalizeUserProfile() defines its shape:

apps/Auth/AuthProvider.php (excerpt)
  public function normalizeUserProfile(array $user): array
  {
    $apps = @json_decode($user['apps'] ?? '');
    if (!is_array($apps)) $apps = [];

    return [
      'id' => (int) ($user['id'] ?? 0),
      'type' => (int) ($user['type'] ?? 0),
      'login' => (string) ($user['login'] ?? ''),
      'email' => (string) ($user['email'] ?? ''),
      'first_name' => (string) ($user['first_name'] ?? ''),
      'last_name' => (string) ($user['last_name'] ?? ''),
      'is_active' => (bool) ($user['is_active'] ?? false),
      'force_signout' => (bool) ($user['force_signout'] ?? false),
      'language' => (string) ($user['language'] ?? false),
      'timezone' => (string) ($user['timezone'] ?? false),
      'position' => (string) ($user['position'] ?? false),
      'photo' => (string) ($user['photo'] ?? false),
      'permissions' => (string) ($user['permissions'] ?? false),
      'apps' => (string) ($user['apps'] ?? ''),
      'password_last_change' => (string) ($user['password_last_change'] ?? ''),
      'APPS' => (array) $apps,
      'ROLES' => (array) ($user['ROLES'] ?? []),
      'TEAMS' => (array) ($user['TEAMS'] ?? []),
      'DEFAULT_COMPANY' => (array) ($user['DEFAULT_COMPANY'] ?? []),
    ];
  }

APPS is the list of apps enabled for the user, ROLES the IDs of the roles and TEAMS the teams (used by the ownership of records).

Working with the user in code

$auth = $this->authProvider();

$auth->getUserId();               // 0 when nobody is signed in (e.g. in a cron)
$auth->getUserType();             // User::TYPE_*
$auth->getUserRoles();            // [1, 3]
$auth->userHasRole(UserRole::ROLE_MANAGER);
$auth->getUserEmail();
$auth->getUserLanguage();
$auth->isTeamMember($idTeam);
$auth->getUser();                 // the whole array from the session

NOTE There is no signed-in user in a cron or the command line, so getUserId() returns 0. Code that is also run from a cron must not depend on it. This is why Notifications\Sender::send() does not work from crons.

The sign-in flow

All of it happens in Hubleto\Erp\Renderer and AuthProvider, on every request:

  1. authenticateRememberedUser() signs the user in when a valid "remember me" cookie exists.
  2. The router parses the URL. The URL parameter sign-out calls signOut().
  3. auth() is called. When nobody is in the session and the parameters login and password arrive (the sign-in form sends them), auth() finds the users with the login (findUsersByLogin()), verifies the password (verifyPassword()), loads the user (loadUser()) and stores it in the session with signIn(). With the parameter session-persist it also creates the remember-me token (initiateRememberMe()). A wrong login sets the cookie incorrectLogin.
  4. The renderer checks the permission of the controller (see Permissions). When it is not granted, the user sees the NotEnoughPermissions page.
apps/Auth/AuthProvider.php (excerpt)
  public function authenticateRememberedUser(): bool {

    /** @var Token */
    $mToken = $this->getModel(Token::class);
    $tokenId = $mToken->validateToken(
      $_COOKIE[$this->config()->getAsString('accountUid') . '-rememberMe'] ?? '',
      Token::TOKEN_TYPE_USER_REMEMBER_ME
    );

    if ($tokenId !== false) {

      /** @var UserHasToken */
      $mUserHasToken = $this->getModel(UserHasToken::class);
      $userHasToken = $mUserHasToken->record->where('id_token', $tokenId)->first();

      if (!empty($userHasToken)) {
        $userId = $userHasToken['id_user'];

        /** @var User */
        $userModel = $this->getModel(User::class);

        $this->logger()->info("Authenticating user {$userId} as 'rememberMe'.");

        $authResult = $userModel->loadUser($userId);
        $this->signIn($authResult);

        return true;
      }
    }

    return false;
  }

Extending the user

A custom app cannot change the User model. When your app needs more data about the user, create its own model with an id_user lookup (as HrEmployees does for employees) and show it in the form of your app. If you need an extra column in the table of users, use a custom column.