REST API authorizations
Permissions to access Bonita platform’s REST API resources are handled by a configurable authorizations mechanism.
The Bonita Portal, or any application that uses the Web REST API, enables user to access resources. The set of resources that a user can access is determined by default by the user’s profiles. This authorization mechanism ensures that users can only access the appropriate resources. This means, for example, that a user with only the User profile cannot access REST API resources intended for the Administrator.
The authorization mechanism is a "white list" implementation. It has two phases, a dynamic phase, which uses a script to grant or deny authorization, and a static phase, which checks in the static configuration to see whether the user is authorized to access a given resource. The static configuration is fixed when the application server is started.
To access a given resource, a user must have a certain permission. These permissions are defined as simple permissions, which are the smallest units of permission, or compound permissions, which are groups of simple permissions. Upon login, a user is granted a set of permissions. These permissions define the set of resources the user is authorized to access.
Summary
For a new Bonita installation, a basic set of authorization checks is activated by default for the REST API. But you might also want to deactivate the engine HTTP API. Here are the key points of the authorizations configuration :
-
The static checks (activated by default on a fresh installation) create an authorization layer that exactly matches the standard Bonita Portal features and profiles. If you are using the standard Portal, you do not need to configure anything.
-
If you want to add extra authorization restrictions based on business rules, turn on the dynamic checks that you want. The configuration file defines standard rules for the most frequent cases, so all you need to do is uncomment the rules you want to apply.
-
If you add a custom page, include a resources=[ list ] in your page.properties to specify which resources your custom page requires the users to have access to.
-
If the previous points do not meet your security needs, you can still manually customize the configuration and rules as much as you want.
If you have migrated your platform from a version of Bonita earlier than 6.4.0, security is deactivated by default. You need to add authorization to your custom pages before you activate authorization.
In Bonita Cloud, to change the properties files and apply Rest Authorization, you just have to open a support ticket and we’ll apply the change on the requested environment for you ! |
Static authorization checking
The static phase uses a set of configuration files:
-
resources-permissions-mapping-*.properties
-
compound-permissions-mapping-*.properties
-
custom-permissions-mapping.properties
These files grant permissions to sets of users based on profile or user name. You cannot remove permissions in a configuration file, so you must ensure that the default definitions grant the minimum permissions that you want to give to any user.
The default versions of these files are located in setup/platform_conf/initial/tenant_template_portal
.
In order to change the configuration on an installation whose platform has already been initialized, use the platform setup tool to
retrieve the current configuration and update the files in setup/platform_conf/current/tenants/[tenantId]/tenant_portal
.
Then use the tool again to save your changes into the database.
Resources permissions mapping
The resources-permissions-mapping-*.properties
files define the mapping of REST resources to simple permissions.
This tells you what permission is needed to access a given resource.
For example: GET|identity/user=[organization_visualization,organization_management]
This specifies that a user with the organization_visualization, or organization_management permissions can see information about users.
By default, this file contains a mapping of each Bonita resources to at least one simple permission.
You can modify the file resources-permissions-mapping-custom.properties
to add your own mappings.
For example: GET|identity/user/3=[organization_management]
This specifies that information about the user with id 3 can only be seen by users with the Organization_management permission.
If there are conflicts between permissions, the more restrictive definition overrides the more general. You can see this in the example above, where the specific permission for user 3 overrides the general permission for seeing user information.
Do not modify file |
Compound permissions mapping
The compound-permissions-mapping-*.properties
files define sets of simple permissions that are grouped together into a compound permission.
You can use a compound permission as "shorthand" for a list of simple permissions.
By default, the file compound-permissions-mapping.properties
contains a compound permission that corresponds to each page of the Bonita Portal,
including custom pages.
For example: userlistingadmin=[profile_visualization, process_comment, organization_visualization, tenant_platform_visualization, organization_management]
This specifies the REST API permissions that are granted with the Bonita Portal Administrator page that lists all the users in the tenant.
By default, there is a compound permission defined for each page in the standard Bonita Portal and there is also one for each provided custom page.
When you install a custom page in the portal, if the page declares its resources properly, then a new compound permission will be added in an internal version
of this file (compound-permissions-mapping-internal.properties
). Then all the users being able to access this page (because it is part of a custom profile or
Living Application they have access to) will also be automatically granted the necessary permissions to call the required REST API resources.
Do not modify file |
Custom permissions mapping
The custom-permissions-mapping.properties
file contains custom rules that supplement the resource permissions and compound permissions.
By default, this file is empty, because the compound permissions definitions automatically manage the permissions needed for default and custom profiles, and for default and custom pages.
If you want to override the default behavior, you can add a rule to this file. You can add a simple or compound permission to a profile.
For example, to give users with the User profile the ability to manage the Bonita Portal look & feel: profile|User=[look_and_feel]
You can also assign a permission to a specific user. This is useful if you want to give a named user access to a resource that is not accessible through the user’s profiles.
For example, if the user John.Smith is assigned the User profile, he does not have permission to manage the portal Look & Feel.
You can add this specific permission to custom-permissions-mapping.properties
by adding this line: user|John.Smith=[look_and_feel]
This means that in addition to the permissions given to him by the User profile, John.Smith can also manage the Portal Look & Feel. It does not modify the permissions for any other user.
If you do not use Bonita Portal but still want to manage REST API authorizations, you can do this using the custom-permissions-mapping.properties
file.
To do this, create a custom profile and configure the relevant permissions.
For example, you could create a profile called CustomProcessManager and assign the permissions needed to monitor and manage processes:
profile|MyCustomProfile=[process_visualization, process_management, process_manager_management, custom_process_manager_permission]
In this example, the custom_process_manager_permission
can be defined in the compound-permissions-mapping-custom.properties
file.
Dynamic authorization checking
If the static authorization checks are not suitable for your applications, you can override the rules as you want using dynamic checks. A user is then granted a permission only if the dynamic check authorizes it. A dynamic check is implemented as sequence of conditions, including a Groovy script. This enables you to tailor the permissions needed to access a resource using dynamic information related to processes.
A dynamic authorization check for a resource is specified by a line in the file dynamic-permissions-checks-custom.properties
.
The line specifies the checks to be made for a request type for a method.
There can be several terms in the line. Checking stops when the system returns success, indicating that the user is authorized.
For example: POST|bpm/case=[user|william.jobs, user|walter.bates, profile|Administrator, profile|User, check|org.bonitasoft.permissions.CasePermissionRule]
This specifies that a POST action can be done for a case resource if the user is william.jobs or walter.bates, or any user with the Administrator profile, or any user with the User profile, or if the CasePermissionRule grants authorization.
A check
term indicates the name of a class to be called. The class must implement org.bonitasoft.engine.api.permission.PermissionRule
.
This example defines a dynamic check that is made whenever a user makes a GET request for the "bpm/case" resource.
If the script returns true
, the user is authorized. If the script returns false
or any other result (including an error), the user is not authorized.
The dynamic-permissions-checks.properties
file contains a placeholder line for each method and resource. For example:
## CasePermissionRule
#GET|bpm/case=[profile|Administrator, check|org.bonitasoft.permissions.CasePermissionRule]
#POST|bpm/case=[profile|Administrator, check|org.bonitasoft.permissions.CasePermissionRule]
#DELETE|bpm/case=[profile|Administrator, check|org.bonitasoft.permissions.CasePermissionRule]
#GET|bpm/archivedCase=[profile|Administrator, check|org.bonitasoft.permissions.CasePermissionRule]
To specify a dynamic check for a method and resource, uncomment the line in the file dynamic-permissions-checks-custom.properties
and add the conditions.
If you specify a condition that calls a Groovy script, you must add the new script:
If the platform has never been started yet:
-
add the script to the
setup/platform_conf/initial/tenant_template_security_scripts
folder -
it will be pushed to database at first run
If the platform has already been started:
-
use the platform setup tool to retrieve the current configuration
-
add the script to the
setup/platform_conf/current/tenants/[tenantId]/tenant_security_scripts
folder -
then use the platform setup tool again to push the new / modified scripts to database
The tenant_security_scripts
folder contains a script sample that can be used to write your own.
Bonita also provides default scripts that should fit common usages. They are packages internally in the binaries, but the
source code is available.
These provided scripts can be used as a base for you own scripts.
If you write your own scripts:
-
make sure you either inherit from an existing rule, or implement the PermissionRule interface, by overriding the isAllowed() method
-
make sure you use the default package declaration at the top of your groovy class (no
package
keyword used) -
make sure this .groovy file is placed in the default directory, under 'initial/tenant_template_security_scripts/' if the platform has never been started, or under 'current/tenants/TENANT_ID/tenant_security_scripts/' if the platform has already been started
Do not modify file |
Example dynamic check script
This script is an example of how to write a dynamic check. It checks two conditions, depending on the method called for a case.
If the method is a POST, which would start a case of a process. the user can only start the case if they are eligible to start the process itself.
If the user action triggers a GET, the user can view the case information only if they are involved in the case.
The Engine API Java method isInvolvedInProcessInstance
is used to check whether the user is involved. For an archived case, the only check possible is whether the user started the case.
import org.bonitasoft.engine.api.*
import org.bonitasoft.engine.api.permission.APICallContext
import org.bonitasoft.engine.api.permission.PermissionRule
import org.bonitasoft.engine.bpm.process.ArchivedProcessInstanceNotFoundException
import org.bonitasoft.engine.identity.User
import org.bonitasoft.engine.identity.UserSearchDescriptor
import org.bonitasoft.engine.search.SearchOptionsBuilder
import org.bonitasoft.engine.search.SearchResult
import org.bonitasoft.engine.session.APISession
import org.json.JSONObject
class CasePermissionRule implements PermissionRule {
@Override
boolean isAllowed(APISession apiSession, APICallContext apiCallContext, APIAccessor apiAccessor, Logger logger) {
long currentUserId = apiSession.getUserId()
if ("GET".equals(apiCallContext.getMethod())) {
return checkGetMethod(apiCallContext, apiAccessor, currentUserId, logger)
} else if ("POST".equals(apiCallContext.getMethod())) {
return checkPostMethod(apiCallContext, apiAccessor, currentUserId, logger)
}
return false
}
private boolean checkPostMethod(APICallContext apiCallContext, APIAccessor apiAccessor, long currentUserId, Logger logger) {
def body = apiCallContext.getBodyAsJSON()
def processDefinitionId = body.optLong("processDefinitionId")
if (processDefinitionId <= 0) {
return false;
}
def processAPI = apiAccessor.getProcessAPI()
def identityAPI = apiAccessor.getIdentityAPI()
User user = identityAPI.getUser(currentUserId)
SearchOptionsBuilder searchOptionBuilder = new SearchOptionsBuilder(0, 10)
searchOptionBuilder.filter(UserSearchDescriptor.USER_NAME, user.getUserName())
SearchResult<User> listUsers = processAPI.searchUsersWhoCanStartProcessDefinition(processDefinitionId, searchOptionBuilder.done())
logger.debug("RuleCase : nb Result [" + listUsers.getCount() + "] ?")
def canStart = listUsers.getCount() == 1
logger.debug("RuleCase : User allowed to start? " + canStart)
return canStart
}
private boolean checkGetMethod(APICallContext apiCallContext, APIAccessor apiAccessor, long currentUserId, Logger logger) {
def processAPI = apiAccessor.getProcessAPI()
def filters = apiCallContext.getFilters()
if (apiCallContext.getResourceId() != null) {
def processInstanceId = Long.valueOf(apiCallContext.getResourceId())
if (apiCallContext.getResourceName().startsWith("archived")) {
//no way to check that the were involved in an archived case, can just show started by
try {
return processAPI.getArchivedProcessInstance(processInstanceId).getStartedBy() == currentUserId
} catch(ArchivedProcessInstanceNotFoundException e) {
logger.debug("archived process not found, "+e.getMessage())
return false
}
} else {
def isInvolved = processAPI.isInvolvedInProcessInstance(currentUserId, processInstanceId)
logger.debug("RuleCase : allowed because get on process that user is involved in")
return isInvolved
}
} else {
def stringUserId = String.valueOf(currentUserId)
if (stringUserId.equals(filters.get("started_by")) || stringUserId.equals(filters.get("user_id")) || stringUserId.equals(filters.get("supervisor_id"))) {
logger.debug("RuleCase : allowed because searching filters contains user id")
return true
}
}
return false
}
}
Initialization
After the application server starts, the first time that one of the configuration files is accessed, the information from all the files is cached in memory for fast access. If you update a file, the changes become active the next time the application server restarts. In your development environment, you can use the debug mode to makes any changes to the configuration files and dynamic check scripts available immediately.
User login
When a user logs in, after the user is authenticated, a map of LoggedUserPermissions
is created.
LoggedUserPermissions
is a combination of the information from compound-permissions-mapping.properties
and
CustomUserPermissionsMapping
that is relevant to the user.
It takes into account all the profiles assigned to the user, not only the current profile, so when you change profile the map does not need to be recreated.
Runtime behavior
At runtime, when a user requests access to a resource, the system checks to see if a dynamic check is defined for this resource. If so, it executes the check, and the result grants or denies the user access to the resource.
If there is no dynamic check for the resource, the system uses the static checks: it uses the information in the ResourceRequiredPermissions
to see what permissions are
needed to access the resource (or page), and checks the LoggedUserPermissions
to see whether the user has the necessary permissions.
If so, the user is authorized.
Otherwise, access is refused.
If access is not authorized, a message is written in the log so that the Administrator is aware that an unauthorized user has tried to gain access.
Note that this level of logging is only available if you set the logging level to FINEST
.
Authorizing access to a custom page
When a new custom page is added, the permissions defined in the page properties are added to the permissions configuration files and the cache. It is not necessary to restart the applications server to activate security for the new custom page. Depending on the permissions that a user of the page already has, it might be necessary to log out and log in again to get access to the new custom page.
If the page declares resources provided by a REST API extension, then the REST API extension must be deployed before the page, otherwise the compound permissions won’t be automatically created when deploying the page, and you will need to redeploy the page after deploying the REST API extension. |
Authorization and custom profiles
When a new custom profile is created, the permissions mappings are updated in the configuration files and in the cache. It is not necessary to restart the application server to activate security for the new custom profile.
Granting permissions to a given resource
If you only develop custom pages and you declare the resources they use properly, you should never have to create custom permissions. However, you may need to do so if you need to manually grant permissions to a given REST API resource (so that it can be called programmatically for example). In order to do that, you need to:
-
Look into the file
resources-permissions-mapping.properties
for the permissions that grant access to the resource. For example, in order to perform a GET onbpm/task
, I can see that I need the permissionflownode_visualization
(syntax:GET|bpm/task=[flownode_visualization]
) -
Edit the file
custom-permissions-mapping.properties
to give the permissionflownode_visualization
to the required profiles or users. For example, to add the permission to the user walter.bates (username), add the following line :user|walter.bates=[flownode_visualization]
Restricting access to a BDM object or its attributes
Starting with the Bonita efficiency subscription edition, you can use a simpler mechanism to grant or deny access to BDM objects or some of their attributes to specific profiles, using the BDM Access Control feature. It is also possible to protect instances of the BDM objects, using REST API authorizations. For more details see : BDM access control
Activating and deactivating authorization
security-config.properties
contains a Boolean property that specifies whether authorization is activated. To activate authorization, set this property to true
: security.rest.api.authorizations.check.enabled true
To activate authorization, edit security-config.properties
and set the value of the security.rest.api.authorizations.check.enabled
property to true
, then restart the application server.
To deactivate authorization, set the property to false
, then restart the application server.
If you activate authorization, you must also deactivate the HTTP API, so that is cannot be used to bypass the authorization settings.
To do this, you can either filter the HTTP API in the Tomcat configuration (that is, accept only specific IP addresses), or you can
deactivate the HttpAPIServlet
. To deactivate the servlet, go to the webapps/bonita/WEB-INF
folder of your web server,
edit web.xml
and comment out the following definitions:
<!-- For engine HTTP API -->
<!--
<servlet>
<servlet-name>HttpAPIServlet</servlet-name>
<servlet-class>org.bonitasoft.engine.api.internal.servlet.HttpAPIServlet</servlet-class>
</servlet>
-->
<!--
<servlet-mapping>
<servlet-name>HttpAPIServlet</servlet-name>
<url-pattern>/serverAPI/*</url-pattern>
</servlet-mapping>
-->
Running in debug mode
If debug mode is activated, whenever you update a configuration file or a dynamic check script, the changes take effect immediately.
To activate debug mode, edit security-config.properties
and set the value of the security.rest.api.authorizations.check.debug
property to true
, then restart the application server.
To deactivate authorization, set the property to false
, then restart the application server. Debug mode should be deactivated in production, so as not to impact performance.
Migration
When you migrate from a version earlier than 6.4.0, authorization is configured to be off (security.rest.api.authorizations.check.enabled
is set to false
).
If you have an existing custom page and want to activate authorization, you need to add permissions to the definition of the custom page. To add authorization to an existing custom page:
-
Export the custom page.
-
Update the page properties with permissions.
-
Activate authorization, by editing
security-config.properties
and setting the value of thesecurity.rest.api.authorizations.check.enabled
property totrue
. -
Restart the application server.
-
Import the custom page.
If you have an existing custom profile, the permissions relating to the profiles is automatically added to the permissions files, so you do not need to update the profile. However, if a custom profile use a custom page, you must update the custom page definition to add permissions before you activate authorization.
Permissions and resources
The table below shows the default permissions and the resources to which they grant access.
Permission | Resources |
---|---|
activity_visualization |
[GET|bpm/processResolutionProblem]| |
application_management |
[POST|living/application, PUT|living/application, DELETE|living/application, POST|living/application-page, PUT|living/application-page, DELETE|living/application-page, POST|living/application-menu, PUT|living/application-menu, DELETE|living/application-menu]| |
application_visualization |
[GET|living/application, GET|living/application-page, GET|living/application-menu]| |
bdm_management |
[POST|tenant/bdm]| |
bdm_visualization |
[GET|bdm/businessData, GET|bdm/businessDataReference]| |
bpm_monitoring_management |
[POST|monitoring/report, DELETE|monitoring/report]| |
bpm_monitoring_visualization |
[GET|monitoring/report]| |
case_delete |
[DELETE|bpm/case, DELETE|bpm/archivedCase]| |
case_management |
[POST|bpm/case, PUT|bpm/caseVariable, PUT|bpm/caseDocument, POST|bpm/caseDocument, DELETE|bpm/caseDocument, DELETE|bpm/archivedCaseDocument]| |
case_start |
[PUT|bpm/process, POST|bpm/case]| |
case_start_for |
[PUT|bpm/process]| |
case_visualization |
[GET|bpm/case, GET|bpm/archivedCase, GET|bpm/caseVariable, GET|bpm/caseDocument, GET|bpm/archviedCaseDocument]| |
command_management |
[POST|bpm/command, PUT|bpm/command, DELETE|bpm/command]| |
command_visualization |
[GET|bpm/command]| |
connector_management |
[PUT|bpm/process, PUT|bpm/processConnector, PUT|bpm/connectorInstance]| |
connector_visualization |
[GET|bpm/process, GET|bpm/processConnector, GET|bpm/processConnectorDependency, GET|bpm/connectorInstance, GET|bpm/archivedConnectorInstance, GET|bpm/connectorFailure]| |
demo_permission (since 7.0.0) |
[GET|extension/demo/getExample, GET|extension/demo/headerExample, GET|extension/demo/logExample, GET|extension/demo/soapExample, GET|extension/demo/xmlExample, POST|extension/demo/postExample]| |
document_management |
[PUT|bpm/caseDocument, POST|bpm/caseDocument, DELETE|bpm/caseDocument, PUT|bpm/archivedCaseDocument, POST|bpm/archivedCaseDocument, DELETE|bpm/archivedCaseDocument, POST|bpm/document, PUT|bpm/document, DELETE|bpm/document]| |
document_visualization |
[GET|bpm/caseDocument, GET|bpm/document, GET|bpm/archiveddocument, GET|bpm/archivedCaseDocument]| |
flownode_management |
[PUT|bpm/flowNode, PUT|bpm/activity, PUT|bpm/task, PUT|bpm/timerEventTrigger]| |
flownode_visualization |
[GET|bpm/processResolutionProblem, GET|bpm/flowNode, GET|bpm/activity, GET|bpm/task, GET|bpm/activityVariable, GET|bpm/archivedFlowNode, GET|bpm/archivedActivity, GET|bpm/archivedTask, GET|bpm/timerEventTrigger]| |
license |
[GET|system/license]| |
look_and_feel |
[POST|portal/theme, PUT|portal/theme, POST|userXP/theme, PUT|userXP/theme]| |
organization_management |
[POST|identity/user, PUT|identity/user, DELETE|identity/user, POST|identity/personalcontactdata, PUT|identity/personalcontactdata, POST|identity/professionalcontactdata, PUT|identity/professionalcontactdata, POST|identity/role, PUT|identity/role, DELETE|identity/role, POST|identity/group, PUT|identity/group, DELETE|identity/group, POST|identity/membership, PUT|identity/membership, DELETE|identity/membership, POST|customuserinfo/definition, DELETE|customuserinfo/definition, PUT|customuserinfo/value]| |
organization_visualization |
[GET|identity/user, GET|identity/personalcontactdata, GET|identity/professionalcontactdata, GET|identity/role, GET|identity/group, GET|identity/membership, GET|customuserinfo/user, GET|customuserinfo/definition, GET|customuserinfo/value]| |
platform_management (since 7.1.0) |
[GET|platform/license]| |
process_actor_mapping_management |
[PUT|bpm/process]| |
process_actor_mapping_visualization |
[GET|bpm/process]| |
process_categories |
[GET|bpm/process, PUT|bpm/process, POST|bpm/processCategory, DELETE|bpm/processCategory, GET|bpm/category, POST|bpm/category, PUT|bpm/category, DELETE|bpm/category]| |
process_comment |
[GET|bpm/comment, POST|bpm/comment, GET|bpm/archivedComment]| |
process_deploy |
[POST|bpm/process, DELETE|bpm/process]| |
process_management |
[PUT|bpm/process, GET|bpm/processConnector, PUT|bpm/processConnector, GET|bpm/processConnectorDependency, POST|bpm/processCategory, DELETE|bpm/processCategory, GET|bpm/processParameter, PUT|bpm/processParameter, POST|bpm/actorMember, PUT|bpm/actorMember, DELETE|bpm/actorMember]| |
process_manager_management |
[POST|bpm/processSupervisor, DELETE|bpm/processSupervisor, POST|bpm/actorMember, PUT|bpm/actorMember, DELETE|bpm/actorMember]| |
process_manager_visualization |
[GET|bpm/processSupervisor, GET|bpm/actorMember]| |
process_visualization |
[GET|bpm/process, GET|bpm/actor, GET|bpm/actorMember, GET|bpm/diagram]| |
profile_management |
[POST|portal/profile, PUT|portal/profile, DELETE|portal/profile, POST|portal/page, PUT|portal/page, DELETE|portal/page, POST|portal/profileEntry, PUT|portal/profileEntry, DELETE|portal/profileEntry, POST|userXP/profile, PUT|userXP/profile, DELETE|userXP/profile, POST|userXP/profileEntry, PUT|userXP/profileEntry, DELETE|userXP/profileEntry]| |
profile_visualization |
[GET|portal/profile, GET|portal/bonitaPage, GET|portal/page, GET|portal/profileEntry, GET|userXP/profile, GET|userXP/profileEntry, GET|userXP/bonitaPage]| |
profile_member_visualization |
[GET|portal/profileMember, GET|userXP/profileMember]| |
profile_member_management |
[POST|portal/profileMember, DELETE|portal/profileMember, POST|userXP/profileMember, DELETE|userXP/profileMember]| |
task_management |
[PUT|bpm/humanTask, PUT|bpm/userTask, POST|bpm/hiddenUserTask, DELETE|bpm/hiddenUserTask, POST|bpm/manualTask, PUT|bpm/manualTask]| |
task_visualization |
[GET|bpm/humanTask, GET|bpm/userTask, GET|bpm/hiddenUserTask, GET|bpm/manualTask, GET|bpm/archivedHumanTask, GET|bpm/archivedUserTask, GET|bpm/archivedManualTask]| |
tenant_platform_management |
[PUT|system/tenant, POST|platform/platform, PUT|platform/platform, DELETE|platform/platform, POST|platform/tenant, PUT|platform/tenant, DELETE|platform/tenant]| |
tenant_platform_visualization |
[GET|system/session, GET|system/log, GET|system/tenant, GET|system/feature, GET|system/monitoring, GET|system/i18nlocale, GET|system/i18ntranslation, GET|platform/platform, GET|platform/jvmDynamic, GET|platform/jvmStatic, GET|platform/systemProperty, GET|platform/tenant ] |