-
Notifications
You must be signed in to change notification settings - Fork 385
Description
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
getHighestGroupedActionfunction 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
newto 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
Accessclass inGibbon/Authnamespace (bundling Authentication and Authorization in this namespace).- Create public static
Access::isAllowedandAccess::getHighestLevelmethods. - This is a slight change in the terminology, with the aim for a more fluent syntax.
- Create public static
- Initialize the
Accessclass in the CoreServiceProvider with anAccess::initializemethod 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
Accessmethods if not initialized. - Construct the
Accessclass 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
getModuleNameandgetActionName, 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.