In COREmanager, access to functions can be defined in two ways:
- by the developer based on user permissions level;
- by the administrator with use of an access control module.
Unavailable functions are automatically removed from the interface. Requesting such functions through InternalCall is ignored and returns an empty XML, while requesting through API returns an error.
Access to functions
Developer determines which user will have access to a particular function of the control panel. This can be done in code: in the Class_isp_api::Action class constructor or via redefining the Authorize, CheckAccess virtual methods, or via XML.
By default, Authorize checks for the current user level in the mask provided in the class constructor. The mask can be further constrained via the @level attribute in the corresponding metadata element. If the check is successful, the CheckAccess method is requested, which is executed successfully by default.
All external functions are available to all users by default. Access to them can only be defined via the @level attribute.
Access control module
COREmanager has a built-in flexible access control system. It allows you to accomplish the following tasks:
- restricting access to control panel functions;
- restricting access to form and list fields;
- restricting access to list entries.
Permissions can be set for both the user and the group, which can include users with the same level of access.
To make it possible to control access of a particular user in the panel, add the following button to your list of users:
<toolbtn name="rights" func="userrights" type="editlist" img="t-rights"/>
The entry ID must contain the username used for authorization. Otherwise, you need to determine how the identifier can be converted into a username. You can write your own RIGHTS_CALLBACK event processing module or use the Class_isp_api::RightsCallback class.
The functions in the userrights group check if the current user has enough permissions to access the selected account by requesting the IsOwner function.
The initial list of functions is formed on the basis of the user menu; the toolbar of the selected list is used for nested lists.
The system imposes a number of restrictions when working with access permissions:
- user cannot change the permissions for himself;
- you cannot change the permissions of a user with access level lvSuper (30) or higher;
- you cannot change permissions for a user whose level is higher than that of the current user.
Functions
COREmanager allows you to create:
- users who by default have access to all functions according to their access level;
- users who only have access to certain functions. This is configured through the "Policy" button in the "Access to functions" section.
The functions available to users with lvPublic access are independent of policy or access settings. As a rule, these are service functions. For example, desktop, su, keepalive, etc. Restricting them can lead to incorrect operation of the web interface.
Function groups
For convenience, the functions of the control panel are grouped by name. A dot is used as the separator. For example, the group of functions with the name user will include the functions: user.edit, user.delete, user.delete.one, while the userrights function will not be in that group. Meanwhile, user.delete.one is included both in the user group and the user.delete group.
If you change access to a function, access to all functions in the group of the same name changes. For example, if you restrict access to the user function, this prohibition automatically applies to all functions whose name begins with the string "user".
If you set access to a function, this action automatically overrides the access settings made for the group the function belongs to.
Access to list elements
COREmanager allows you to restrict access not only to functions, but also to individual list entries. To do this, the list must have the ability to set filters. The permissions module uses a filter form (the function named .filter is used). The filter set through the rights module is invisible to the user and cannot be removed. The fields for which the list was restricted will be hidden on the filter form when it is opened by the user.
If a filter is applied to a list, presence of a filter is checked when editing the items in that list. To do this, the list function is requested (the name of the list is taken from the form function name by discarding the right part of the name until the dot) with the parameter has_record=<record identifier> (the HasRecord method of the ListAction class). The result should be similar to the list function call, but can only contain one record with the requested identifier, if such a record is available.
Nested lists
To check access to items in nested lists, all base lists are checked, whose names are calculated by consecutively discarding the right part of the function name until the dot. The ID of the entry in the base list is calculated from the plid parameter. It is assumed that the identifiers of all the base lists are listed in plid and separated by "/".
Group permissions
In addition to converting a user ID into a name, the userrights.user function performs a number of other tasks that allow you to work with user groups. By default, this feature is not available because COREmanager does not allow you to work with specific users.
To use this feature, you must add your function/event processing module RIGHTS_CALLBACK. This function is used for the following:
- to convert a user ID into a username;
- to get the list of permission levels used in the control panel;
- to get the list of users with a specified permission level.
The Class_isp_api::RightsCallback class is included in the API to simplify the implementation of such processing modules.
For example, you have a list of admin users implemented by the admin function. Then you can create an instance of Class_isp_api::RightsCallback class by passing the necessary parameters to it:
#include <api/module.h>
#include <api/rights.h>
namespace {
...
MODULE_INIT(admin, "") {
new isp_api::RightsCallback(isp_api::lvAdmin, "admin", "name");
}
} // end of private namespace
Mixed user lists
In some cases, the function provides the list of users for several access levels at once. To use such a list as a source for the Class_isp_api::RightsCallback class, you must specify access level values for the entries. The last parameter of the Class_isp_api::RightsCallback class constructor can be used for this. This is an XPath expression that is used to select entries from an XML document returned by a function that implements the work with the user list.
For example, you have the users list that includes both users with lvUser access level and administrators with lvAdmin access level. You can implement the work both with the group of users and with the group of administrators as follows:
#include <api/module.h>
#include <api/rights.h>
namespace {
...
MODULE_INIT(users, "") {
// Suppose the list has the level column, which specifies the entry level
new isp_api::RightsCallback(isp_api::lvAdmin, "users", "name", "", "/doc/elem[string(level)='admin']/");
new isp_api::RightsCallback(isp_api::lvUser, "users", "name", "", "/doc/elem[string(level)='user']/");
}
} // end of private namespace
Own groups
If your panel architecture uses its own user groups, synchronize the group information with COREmanager.
To do this, it is necessary to implement:
-
creating/deleting/renaming a group;
// create a group named "NewGroup" for users with access level 16 // the access level is set when the group is created and cannot be changed InternalCall(ses, "userrights.group.edit", "level=level_16&name=NewGroup&sok=ok"); // renaming the group with the name "NewGroup" to "MyGroup InternalCall(ses, "userrights.group.edit", "elid=#NewGroup&name=MyGroup&sok=ok"); // deleting the "MyGroup" group InternalCall(ses, "userrights.group.delete", "elid=#MyGroup");
-
adding/removing a user to/from a group;
// adding the "user" user to the "MyGroup" group InternalCall(ses, "userrights.group.users.resume", "elid=user&plid=MyGroup"); // deleting the "user" user from the "MyGroup" group InternalCall(ses, "userrights.group.users.suspend", "elid=user&plid=MyGroup");
-
changing the properties of the group.
// Include all users with the appropriate access level in the "MyGroup" group // The prohibiting/permitting rules set for this group will be in effect // for all users with access level 16 (see previous examples) // Note: This action automatically deletes all previously included users from the group. // If the "default" flag is removed from the group, it will not contain any users InternalCall(ses, "userrights.group.edit", "elid=MyGroup&default=on&sok=ok");
Along with these internal requests, corresponding changes must be made in your own groups.
To avoid desynchronization, it is also necessary to keep track of changes that are made through the userrights functions. For example, as follows:
class AddUserToGroup : public Event {
public:
AddUserToGroup() : Event("userrights.group.users.resume", "mygroup") {}
virtual void AfterExecute(Session &ses) const {
if (ses.conn.isInternal())
return; // internal request
const string &user = ses.Param(ELEM_ID);
const string &group = ses.Param(PARENT_ID);
AddUserToGroup(group, user);
}
};
Converting a user ID into a name
When a user logs in to the system, he enters his username and password. This name is then used to save the user's settings and access permissions. Sometimes when working with a list of users stored in a database, it is easier to use Id of the entry instead of name as the key. Then the key field does not match the username, and the example above does not work. In this case, you need to modify the function/event processing module RIGHTS_CALLBACK:
#include <api/module.h>
#include <api/rights.h>
namespace {
...
MODULE_INIT(admin, "") {
new isp_api::RightsCallback(isp_api::lvAdmin, "admin", "name", "id");
}
} // end of private namespace