Is it possible to complete a User Task with case insensitive UserId in BRMS 5.3.x ?

Solution Unverified - Updated -

Environment

  • Red Hat JBoss BRMS (BRMS)
    • 5.3.x

Issue

  • When authenticating users through LDAP it sometimes allows for mixed casing. For example the userid demo" is the same asDemoand is allowed to be used becauseLDAPallows this. However it appears that if a user was to log-in toBusiness Centralconsole ofBRMS 5.3.xasdemoand claim some user tasks they will be owned bydemo. Later if the same user decides to login asDemo` and try to complete the same task it throws the following exception.
org.jbpm.task.service.PermissionDeniedException: User '[User:'Demo']' does not have permissions to execution operation 'Complete' on task id 4485

But then logging into demo the user would be able to work with the task properly. Is there a way to tell jBPM5 Engine to use a case insensitive version of the ActorId for existing tasks instances?

Resolution

  • It could be possible to make some changes to the internal TaskService calls to change the default behavior of how TaskService checks whether a user is authorized to perform the task or not. Normally, after calling the complete(..) operation from the user's code they call the complete(..) operation on org.jbpm.task.service.local.LocalTaskService.complete(..) to complete a task, which is an implementation of org.jbpm.task.TaskService . So, in this case , first users need to provide their own implementation of this org.jbpm.task.TaskService which would mock the way org.jbpm.task.service.local.LocalTaskService works internally.
package org.jbpm.task.service.local;
...
public class LocalTaskService implements TaskService {

    private org.jbpm.task.service.TaskService service;
    private TaskServiceSession session;

    public LocalTaskService(org.jbpm.task.service.TaskService taskService) {
        this.service = taskService;
        this.session = service.createSession();
    }
...

    public void complete(long taskId, String userId, ContentData outputData) {
        session.taskOperation(Operation.Complete, taskId, userId, null, outputData, null);
    }
...

Once it is done , just use this new implementation inside the user's code (which is being used to complete the tasks) to get taskService object to complete the task.
Now the reason for making this change is that users can not only, give their own implementation of LocalTaskService.complete(...) method , but internally LocalTaskService needs a org.jbpm.task.service.TaskServiceSession object to call the org.jbpm.task.service.TaskServiceSession.taskOperation(...) method to get the User and Group ownership details of the task and check if they are allowed to perform the action on the task or not. This is where the root cause of the above mentioned problem lies.

package org.jbpm.task.service;
...
public class TaskServiceSession {

    private final TaskPersistenceManager tpm;
    private final TaskService service;
...
    public void taskOperation(final Operation operation, final long taskId, final String userId,
                              final String targetEntityId, final ContentData data,
                              List<String> groupIds) throws TaskException {
        OrganizationalEntity targetEntity = null;

        groupIds = doUserGroupCallbackOperation(userId, groupIds);
        doCallbackUserOperation(targetEntityId);
        if (targetEntityId != null) {
            targetEntity = getEntity(OrganizationalEntity.class, targetEntityId);
        }

        final Task task = getTask(taskId);
        User user = getEntity(User.class, userId);

        boolean transactionOwner = false;
        try {
            final List<OperationCommand> commands = service.getCommandsForOperation(operation);

            transactionOwner = tpm.beginTransaction();

            evalCommand(operation, commands, task, user, targetEntity, groupIds);
...

    void evalCommand(final Operation operation, final List<OperationCommand> commands, final Task task,
                     final User user, final OrganizationalEntity targetEntity,
                     List<String> groupIds) throws PermissionDeniedException {

        final TaskData taskData = task.getTaskData();
        boolean statusMatched = false;

        for (OperationCommand command : commands) {
            // first find out if we have a matching status
            if (command.getStatus() != null) {
                for (Status status : command.getStatus()) {
                    if (taskData.getStatus() == status) {
                        statusMatched = true;
                        // next find out if the user can execute this doOperation
                        if (!isAllowed(command, task, user, groupIds)) {  <<<--- This is where the problem occurs --->>>
                            String errorMessage = "User '" + user + "' does not have permissions to execution operation '" + operation + "' on task id " + task.getId();

                            throw new PermissionDeniedException(errorMessage);
                        }
...

    private boolean isAllowed(final OperationCommand command, final Task task, final User user,
                                     List<String> groupIds) {
        final PeopleAssignments people = task.getPeopleAssignments();
        final TaskData taskData = task.getTaskData();

        boolean operationAllowed = false;
        for (Allowed allowed : command.getAllowed()) {
            if (operationAllowed) {
                break;
            }
            switch (allowed) {
                case Owner: {
                    operationAllowed = (taskData.getActualOwner() != null && taskData.getActualOwner().equals(user));
                    break;
                }
                case Initiator: {
                    operationAllowed = (taskData.getCreatedBy() != null && 
                        (taskData.getCreatedBy().equals(user)) 
                         || (groupIds != null && groupIds.contains(taskData.getCreatedBy().getId())));
                    break;
                }
                case PotentialOwner: {
                    operationAllowed = isAllowed(user, groupIds, people.getPotentialOwners());
                    break;
                }
...

    private boolean isAllowed(final User user, final List<String> groupIds, final List<OrganizationalEntity> entities) {
        // for now just do a contains, I'll figure out group membership later.
        for (OrganizationalEntity entity : entities) {
            if (entity instanceof User && entity.equals(user)) {
                return true;
            }
            if (entity instanceof Group && groupIds != null && groupIds.contains(entity.getId())) {
                return true;
            }
        }
        return false;
    }
...

As it can now be seen from the above code flow of TaskServiceSession class, users would need to override the methods like isAllowed(...) to allow ignore-case comparison of the User and Group details from the one which the user has supplied in their code to complete the task with the details that are registered with task's PeopleAssignments.

This solution is part of Red Hat’s fast-track publication program, providing a huge library of solutions that Red Hat engineers have created while supporting our customers. To give you the knowledge you need the instant it becomes available, these articles may be presented in a raw and unedited form.

Comments