Access Groups: Why only one per user?

Org chart is not a metaphor - it is the original idea. That’s why the root element is called “company”. If you use it differently - it would be logical to develop another module. This could be a very good app component :slight_smile: - you are welcome to contribute!

I’m getting a little confused here - @knstvk first said that a rework is in the works, then @stukalov basically said no way, then seemed to say it’s being considered, then in the latest post seems to be saying no again.

Is this access groups feature being considered for a rework into more of a “set of keys” type of thing or no?

Our conversion really requires it and if we have to develop something similar ourselves it will increase conversion time drastically.

We’re in a battle for survival here, so while I agree that would be a great thing to do, we’ve got to get our app converted and out to clients so we can survive as a company. We waited too long - all our fault of course (though we didn’t find a suitable platform till CUBA) - and now we’ve got to go as fast as possible.

I don’t even remotely have any idea how to get started with something like this.

I assumed Access Groups would handle what we need - just by the name - until I realized it’s one AG per user and then it all fell apart.

@jon.craig,

What I can say for sure - it is not in a short-term list, even not in mid-term. These are already fully booked.

Saying that this is one AG per user is not fully right, as the user also has all AGs under the one they belong to.

Regards,
Aleksey

Still - the need is not for a hierarchy - the need is for a completely assortable set of permissions to various records.

To quote a previous post of mine:

This is exactly what I’m saying - you are trying to apply solution that solves a similarly looking problem, but not yours. This is just not right mechanism.

It’s is like trying to use wheels as continuous track - both do almost the same thing, but very differently, so one cannot substitute another one.

Agreed. I guess I’m just mentioning that an enhancement or redesign of this feature might be a good thing. At least two users from the tiny subset of CUBA users that come to the forums could very much use it, so… I don’t think I’m the only one that thought “access groups” meant something very different, and was surprised to find out the truth of it.

This adds a very large amount of time onto our conversion as this type of access control is pervasive in our system (and would be in any medical system really).

Yes, this is why I’ve always had a problem with this hierarchical groups thing. The organisational chart is about who reports to who, who gets paid what and where the buck stops, and usually doesn’t have that much to do with what’s going on in an application. The Finance Director sits at the same level as the IT Director, but I certainly wouldn’t want to give the Finance Director access to the Wipe Data menu item.(This is just an example, by the way. The only person who can wipe data is me :-))

1 Like

Yes, your best bet here is to roll your own access stuff. I set up the flat group structure and then just one-to-one link a profile class to the built in user class. That way, Cuba can still do the login stuff, but then you can do access stuff yourself.

Of course this was a lot easier when I could access the user session from the ui xml files.

hi,

back then at my old company we also hit the limitation of the fact that one user can only be in one group. But in that case, it was a very sophisticated permission requirement that surely not the average business application had.

The solution to get over this limitation was that we used the user & groups not as the source entities for managing the permission but instead as target entities. This is a good example where when you need higher-level abstractions, you can generate the target entities on the fly.

In this case, what we did in order to get over the one-business-user-per-constraint-hierarchy was that we for every business employee created multiple sec$User objects. Those different user objects were put into all the sec$Group instances that they needed. Then we used the great User substitution feature. With that the business user got the user selection drop-down and was able to switch between the target users. In this case, it actually made sense, because those different user instances, in fact, represented Hats that this business user could wear at a time. But normally the user did not wear two hats at a time.

With this implementation I learned an interesting lesson: the limits of the Platform are actually not just what the basic primitives allow, but they can be remote controlled (as they are just data in the DB) in order to make your own abstractions.

Another example where I remote controlled the CUBA building blocks is the very little known entity pin example: GitHub - mariodavid/cuba-component-entity-pin: CUBA component that allows to pin a particular entity to a user session. There I dynamically add session constraints programmatically based on some user-interaction (like pinning an entity instance). There the access groups are also just the starting point.

Besides that, I think that in the case of changing the behavior of the access groups and make it multi-dimensional it can get very complicated very quickly. You can see it in the multi roles concept, where it is from an operations perspective sometimes really hard to determine what actual permissions now apply to a particular user. In 7.2 the screen of the “effective role” was introduced in order to mitigate that problem. Back then I even created a dedicated addon just for that very case of managing the complexity (GitHub - balvi/cuba-component-user-session-information: CUBA component that shows detailed information about user sessions).

For the Access Groups, multi-dimensionality combined with the hierarchical structure will surely make it super hard to understand what particular constraints are actually applying to a particular user.

Also, I think we have to put it a little bit into context here. Comparing CUBAs security subsystem to the golden standard of security in the Java world: Spring security you will quickly see that in a lot of respects CUBA highly exceeds the capabilities of what you can do with Spring security. Even one of the most advanced parts of Spring Security: Spring Security ACL is covered (in a different way) and exceeded by CUBAs security subsystem.

Of course, the security requirements of a business can skyrocket. But then we also have to look at the average (or better the median) security-requirements of a business application. Because flexibility very often comes at the cost of complexity…

Mirroring the behavior of LDAP and with that the behavior of Microsoft Active Directory enables to work a lot of people from the ops domain here. So I think even that on its own has a high value.

I would suggest you that in case you already see that your requirements exceed the limits of CUBA, try to think of how to programmatically generate constraints as I did it in the entity-pin example and apply them on the fly. This way the sky is the limit…

Cheers
Mario

1 Like

Probably, but the problem is I don’t even have a fragment of an inkling on how to do it. I need records a user doesn’t have access to, to appear to not exist - which is what access groups do… but only in the limited fashion like we’ve been discussing.

I have no idea how to do it… I’m guessing one would have to extend DataManager as I’m guessing all the access group stuff is done there?

I wouldn’t even know where to start.

I need records a user doesn’t have access to, to disappear, to basically not exist… everywhere in the system that records for that entity are accessed. It needs to be central… like the access groups are.

My 2 cents…

I completely agree with @mario, CUBA security subsystem rocks :smiley:

Something that I noticed some time ago (after the initial “confusion” I had about access groups), is that your constraints don’t have to be “hardcoded” as constraints in the AccessGroup. You can use database joins and even call standard services (spring beans, etc.) to define if the constraint has to be applied or not for the given user.

Where I work we created a Status Component (something like a simpler BPM) just to define which user (or group of users) is responsible for a given record in a given status. As you can imagine, there are a lot of possible constraints to be applied.

It ended up being very easy to implement: We created a single group for all users with the same constraint, that would call a service (spring bean) that, for any given entity (we created a superclass) would check the entity status, and if the user was assigned as responsible for this specific status, he would be allowed to manipulate the record.

@jon.craig and ray1, if you two create a test project with your domain classes, I can try to help you setting the constraints to achieve what you are calling " “set of keys”.

I did a quick search and there are some information in the docs about it:

https://doc.cuba-platform.com/manual-7.2/local_admins_example.html
https://doc.cuba-platform.com/manual-7.2/constraints.html#constraints_run_time

Regards,
Peteson

1 Like

Take a look at this database script in the addon @mario created:

insert into SEC_CONSTRAINT
(ID, VERSION, CREATE_TS, CREATED_BY, UPDATE_TS, UPDATED_BY, DELETE_TS, DELETED_BY, CHECK_TYPE, OPERATION_TYPE, CODE, ENTITY_NAME, JOIN_CLAUSE, WHERE_CLAUSE, GROOVY_SCRIPT, FILTER_XML, IS_ACTIVE, GROUP_ID)
values ('a122a042-c314-bfb8-affa-d0d31aab2596', 1, '2017-03-09 09:38:17', 'admin', '2017-03-09 09:38:17', null, null, null, 'db', 'read', null, 'entitypin$Order', null, '{E}.customer.id = :session$pinEntity', null, null, true, 'cde8e540-8f74-4c95-7bf4-f6b14e927eb0');

The user will only be able to read (view) orders of the selected (‘pinned’) user. In this case @mario is using a session parameter for the rule. But you can use other entities tables (joins, etc.) and even a service if a complex logic is required.

Regards,
Peterson.

1 Like

Is there an example you could share of how this works? I’m still new enough to this that I can’t work out how that would work, without an example.

I can try to make a test project, but it would end up being extremely complex for a test project. If you look back at my initial post you can see a small example of what our system does.

And keep in mind - a user of the system (an admin user of course!) needs to define all of this, at runtime, to specify which users can see and work with what data.

In order to dynamically inject constraints into the user session you don’t need to touch the dataManager implementation.DataManger is more of a delegation class. For the heavy lifting a lot of the stuff goes on in the RdbmsStore. For the constraints it somehow does a lookup of the constraints to apply through the UserSession class. Therefore the only thing you have to do is to change the constraints that are currently set for the user session.

In the entity pin example (and with the database script @peterson.br mentioned) I really just auto-populated the definition of the constraint configuration. If you start the entity-pin example and look into the Access Groups you see a group called “PinDefinitions”. Below you will find the definitions of what constraints to apply for each Entity (the name of the Entity is actually the name of the Access Group). These are just the configuration that should be applied when a user clicks pin. But this was just my easy way to somewhere store the constraint configuration. It could be anywhere in the application (hard-coded, in another entity etc.).

What happens at the time the user clicks on the screen is that the addon programmatically adds constraints to the user session.

Here you see the one magic line in the whole entity pin example that applies and removes security constraints on the fly: cuba-component-entity-pin/PinEntityServiceBean.groovy at master · mariodavid/cuba-component-entity-pin · GitHub

In CUBA 7.2 this method was replaced by cuba/UserSession.java at master · cuba-platform/cuba · GitHub (it was never really part of any kind of official API). But the newly created concept of ConstraintsContainer will be the better replacement.

From that point onwards - the DataManager will take this constraint into consideration and apply it all over the application. See the screencast when pinning a particular order and then try to use the standard filter component with a dropdown: only the customers that are related to that order are visible in the application:

pin-order

With that understanding you might be able to abstract away from the concrete use-case of entity-pin and see a path to create a dedicated object where you store your constraint-to-some-context configurations.

Then at login time of the user you load all constraints and put them to the user. This is probably one possible path.

If I find some time in the next days, I will give it a shot and create an example app.

Cheers
Mario

1 Like

I’ll have to look into this when I’m back at work on Monday (and @peterson.br’s info too).

Thank you for the pointers for sure!

This is another point-cut where you can define your own constraints - User Logged In event:

package com.company.test2342342134.listeners;

import com.company.test2342342134.core.MyAccessGroup;
import com.company.test2342342134.entity.Foo;
import com.haulmont.cuba.security.app.group.AccessConstraintsBuilder;
import com.haulmont.cuba.security.app.group.AccessGroupDefinitionsComposer;
import com.haulmont.cuba.security.auth.events.UserLoggedInEvent;
import com.haulmont.cuba.security.global.UserSession;
import com.haulmont.cuba.security.group.ConstraintsContainer;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;

import javax.inject.Inject;

@Component("test2342342134_AuthenticationEventListener")
public class AuthenticationEventListener {

    @Inject
    private AccessGroupDefinitionsComposer accessGroupDefinitionsComposer;

    @EventListener
    public void userLoggedIn(UserLoggedInEvent event) {
        UserSession session = event
                .getAuthenticationDetails()
                .getSession();


        if (session.getUser().getLogin().startsWith("a")) {
            ConstraintsContainer newConstraints = AccessConstraintsBuilder.create()
                    .join(session.getConstraints())
                    .withJpql(Foo.class, String.format("{E}.createdBy = '%s'", session.getUser().getLogin()))
                    .build();
            session.setConstraints(newConstraints);
        }
    }
}

In the sample above all users starting with a will be able to see only instances of Foo which were created by them.

Technically here you can apply constraints from different AccessGroups:

ConstraintsContainer newConstraints = AccessConstraintsBuilder.create()
      .join(session.getConstraints())
      .join(accessGroupDefinitionsComposer.composeGroupDefinitionFromDb(myAccessGroupUuid))
      .build();

, where AccessGroupDefinitionsComposer can be simply injected.

Hello guys,

I did a quick test project/app component that implements what I called “Role Constraints”. The idea is very simple: be able to link Access Groups and Roles.

https://github.com/petersonbr/cuba-role-constraints

Key implementation details:

Test steps:

  1. I created a Role to assign Access Groups to:
    image

  2. I created two Access Groups and added constraints:

OnlyAdminUser: Only ‘admin’ user is allowed.

OnlyCreatedByUser: Only users created by the current user are allowed

  1. I linked userRole to OnlyAdminUser Access Group:

  2. I created two new users: test and createdByTest (I used the first user to create the second). Notice that all users are on the default Access Group:

  3. I assigned userRole Role to test User:

  4. I logged in with test User and opened the user browse screen. Only the admin user is shown:
    image

  5. I linked userRole to OnlyCreatedByUser Access Group:
    image

  6. I logged in again with test User and opened the user browse screen. No user was shown, because when these two constraints are applied, no user is allowed.

  7. I removed the link userRole - OnlyAdminUser, logged in again with test User and opened the user browse screen. Now, only the userCreatedByTest User is show!!

image

I hope this quick test I’ve done help you guys somehow. Even complex topics as this one are easily implemented (an can become addon if reuse is required) in CUBA Platform. As @mario told, the sky is the limit :smiley:

Regards,
Peterson.

3 Likes

This is ultimately how I solved this. I used .withInMemory constraints that use custom Predicates to handle what I needed.

1 Like