This content is dedicated to our next version. It is work in progress: its content will evolve until the new version is released. Before that time, it cannot be considered as official.

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. 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 Applications features and profiles. If you are using the Bonita Applications, 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 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 resources-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

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 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 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 programatically for example). 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, 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:

  1. Export the custom page.

  2. Update the page properties with permissions.

  3. Activate authorization, by editing security-config.properties and setting the value of the security.rest.api.authorizations.check.enabled property to true.

  4. Restart the application server.

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

You can find the rest API authorizations in this page.