REST API authorizations

Permissions to access Bonita platform’s REST API resources are handled by a configurable authorizations mechanism.

The Bonita Applications, 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. In Subscriptions editions there are two phases:

  • a dynamic phase, which can use a script to grant or deny access to a resource,

  • a static phase, which is executed if there is no dynamic configuration for the resource. It checks in the static configuration to see whether the user is authorized to access a given resource or not.

In community edition, only the static phase is executed.

The static phase works the following way:
To access a given resource, a user must have one of the permissions associated with the resource. 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. Here are the key points of the authorization’s configuration :

  • The static check creates an authorization layer that exactly matches the standard Bonita Applications features and profiles.

  • In addition in Bonita subscription editions a set of dynamic rules is active by default to set finer grain restrictions mostly based on BPM business rules (process actor mapping, supervisors…​).

If you are using the Bonita Applications, you do not need to configure anything.

  • If you want to add extra authorization restrictions based on custom business rules, you can configure custom static permissions or implement your own dynamic rules (subscription editions).

  • 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.

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 username. You cannot deny permissions in the configuration files (it’s a "white list" mechanism only), 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 can grant you access to 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 resources-permissions-mapping.properties directly, as it is reserved for default values. Custom values should be added manually in file resources-permissions-mapping--custom.properties

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 Applications, including custom pages.

For example: cusompage_adminUserList=[profile_visualization, process_comment, organization_visualization, tenant_platform_visualization, organization_management]

This specifies the REST API permissions that are granted by the Bonita Administrator User List page that lists all the users in the tenant.

By default, there is a compound permission defined for each page that is installed by default in the Bonita Runtime.

When you install a custom page in the Bonita Administrator Application, 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 an application they have access to) will also be automatically granted the necessary permissions to call the required REST API resources.

Do not modify file compound-permissions-mapping.properties directly, as it is reserved for default values. Custom values should be added manually in file compound-permissions-mapping-custom.properties

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 view applications: profile|User=[application_visualization]

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 modify applications. You can add this specific permission to custom-permissions-mapping.properties by adding this line: user|John.Smith=[application_management]

This means that in addition to the permissions given to him by the User profile, John.Smith can also manage the installed applications. It does not modify the permissions for any other user.

If you do not use Bonita Applications 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

For Enterprise, Performance, Efficiency, and Teamwork editions only.

From Bonita version 2022.1, Dynamic authorization checking is enabled by default.

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 a 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]

By default, dynamic checks are enabled.

To completely disable dynamic checks, simply either:

  • set the Environment variable BONITA_RUNTIME_AUTHORIZATION_DYNAMICCHECK_ENABLED=false

  • or add the Java System property -Dbonita.runtime.authorization.dynamic-check.enabled=false to your setEnv[.sh|.bat] tomcat startup script.

To disable a single dynamic check for a method and resource, comment out the corresponding line in the file dynamic-permissions-checks-custom.properties.

To add a custom dynamic check for a method and resource, add your line in the file dynamic-permissions-checks-custom.properties following the example above.

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 dynamic-permissions-checks.properties directly, as it is reserved for examples, and may be overwritten during migration to a newer version. Custom values should be added manually in file dynamic-permissions-checks-custom.properties

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 the resources they use are declared properly, no custom permissions should be created. 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 automatically). In order to do that, you need to:

  1. 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 on bpm/task, I can see that I need the permission flownode_visualization (syntax: GET|bpm/task=[flownode_visualization])

  2. Edit the file custom-permissions-mapping.properties to give the permission flownode_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, deactivate the HTTP API or it will be used to bypass the authorization settings. To do this, you can:

  • either add a Java system property -Dhttp.api=false to file setEnv.[sh|bat] inside tomcat bundle (in folder setup/tomcat_templates/)

  • or set an environment variable HTTP_API=false before launching Bonita Tomcat bundle

Running in debug mode

To optimize performance in production, Bonita caches the dynamic check scripts for faster subsequent executions.

If debug mode is activated, Bonita will reload the dynamic check scripts each time they are executed. It allows to rapidly validate your script at development time.

To activate debug mode, edit bonita-platform-sp.properties and set the value of the bonita.runtime.authorization.dynamic-check.debug property to true, then restart the application server.

Then, each time you change a dynamic check script, simply update it in database using Setup tool (./setup.sh push). The script will be reloaded at next execution (next time you call a URL that matches this dynamic rule).

To deactivate authorization, set the above property to false, then restart the application server. Debug mode should be deactivated in production, so as not to impact performance.

troubleshooting-icon Troubleshooting

To troubleshoot REST API permissions issues, you need to increase the log level to DEBUG (or TRACE for even more logs) for the packages org.bonitasoft.authorization and com.bonitasoft.authorization in order requests attempts to be displayed in the log files bonita-*.log (by default, they are not).

In order to do that in a Tomcat bundle, you need to edit the file `<BUNDLE_HOME>/server/conf/log4j2-loggers.xml.

  • Make sure the following lines are not commented or add them if they are not present :

    <Logger level="TRACE" name="org.bonitasoft.engine.authorization"/>
    <Logger level="TRACE" name="com.bonitasoft.engine.authorization"/>

In Bonita Studio the loggers to see denied REST resources access are already configured in order to help troubleshooting 403 errors.

Common error examples

Symptom: Getting a 403 response for some requests
Possible Solutions:

  • Check the logs and look for a message starting by Unauthorized access to …​.

    • If the access was denied by a dynamic permission rule, the log message will indicate which one

    • If the access was denied by static permissions, the log message will indicate which static permissions can grant access to the resource

  • If the access should be granted for the logged in user, either fix the rule or the static permissions

  • If the access was denied as expected, fix the page that contain the request to update/remove the REST API request

Permissions and resources

You can find the default REST API authorizations in this page.