Skip to content

Discussion: Access Management #1702

@SKuipers

Description

@SKuipers

In reviewing #1673 and #1666, we're at a position in the refactoring to make some architectural decisions regarding access management. Currently, almost all user access to a certain page or permission is determined through the functions isActionAccessible() and getHighestGroupedAction(). These functions use a legacy style access to the database and need deprecated, as seen in #1673 and #1666.

Considerations

  • Going forward, we will ideally be transitioning to using routing rather than filenames for access.
  • The isActionAccessible() function is used widely, and nested in both other functions as well as class methods.
  • The getHighestGroupedAction function uses the precedent field from gibbonAction to determine the highest action within a grouped set.
  • Any access-related class will need access to the database to determine user permissions.
  • The SessionFactory currently makes a determination of the current module and action based on the $_GET['q'] and $_POST['address'] variables, but this isn't the ideal place for it, as it isn't session based and should only exist the lifetime of the script.

The solutions in #1673 and #1666 are a great start. @yookoala I like your approach to parsing out the actions in a testable way, and using an AccessManager class to internalize the calls to the gateway. However, in reviewing them together, I have some thoughts on the proposed code changes:

  • Both isActionAccessible and getHighestGroupedAction are action-related, so ideally could be handled with the same class.
  • In general, I aim to avoid the use of new to create objects, using either the container or factory classes for instantiation. This helps keep class constructors flexible and maintainable for the future.
  • In this particular case, I'd prefer to not create objects inside function parameters, for the sole use of passing them into a function. I wonder if this could be handled internally.
  • It'd be ideal to not add new global variables to the scope, as they are more mutable and easily overwritten.

I've put together some ideas on how we might approach these in a unified manner, while also hopefully laying some groundwork for future routing.

Architectural Suggestions

  • Create a single Access class in Gibbon/Auth namespace (bundling Authentication and Authorization in this namespace).
    • Create public static Access::isAllowed and Access::getHighestLevel methods.
    • This is a slight change in the terminology, with the aim for a more fluent syntax.
  • Initialize the Access class in the CoreServiceProvider with an Access::initialize method to pass in the ActionGateway, rather than in the constructor. This should help with testing, as the gateway or methods could be mocked as needed.
  • Return false from Access methods if not initialized.
  • Construct the Access class with the current action, so that Access::getCurrentAction and Access::getCurrentModule methods return the module and action of the current page, using $_GET['q'] and $_POST['address'] in the interim until we move onto routing refactoring.
  • Deprecate getModuleName and getActionName, having parsed and determined them internally in the Access class.
  • Internally handle whether an action is using a filename (eg users_manage_add.php) or a route path (eg /users/add). This could be done by using the Action class within the Access class.
  • Internally cache when the same action is checked multiple times, to reduce database calls.

Using a class with static methods does have its drawbacks, however in this case as a core service, like Format, I think it may be worth considering. It would give a nice fluent Access::isAllowed syntax, and could be dropped-in to replace functions without any dependencies (just a use declaration).

Here's my thinking for the Access interface contract. Let me know what you think. Alternatively, getCurrentAction could return an Action object, which exposes the getModule and getActionName methods, which may be more clean.

/**
 * Access class contract for determining if users can perform an action based 
 * on the configured permissions for their role.
 *
 * @version  v25
 * @since    v25
 */
interface Access
{
    public static function initialize(Session $session, ActionGateway $actionGateway);

    public static function isInitialized() : bool;

    public static function getCurrentModule() : string;

    public static function getCurrentAction() : string;

    public static function isAllowed(string $module, string $route, ?string $action = null) : bool;

    public static function getHighestLevel(string $module, string $route) : string;
}

@yookoala Let me know what you think. I'm aiming for something that builds on what you've started in your PRs, and ideally takes us one step further in the overall refactoring in the system, while still being backwards-compatible enough for legacy routes.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions