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
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
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.
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:
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
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:
-
RoleAccessGroup entity: Link Access Groups to Roles.
-
AuthenticationEventListener: Add all Access Groups Constraints to user session on login or substitution, based on the RoleAccessGroup data.
Test steps:
-
I created a
Role
to assignAccess Groups
to:
-
I created two
Access Groups
and added constraints:
OnlyAdminUser: Only ‘admin’ user is allowed.
OnlyCreatedByUser: Only users created by the current user are allowed
-
I linked
userRole
toOnlyAdminUser
Access Group:
-
I created two new users:
test
andcreatedByTest
(I used the first user to create the second). Notice that all users are on the default Access Group:
-
I assigned
userRole
Role totest
User:
-
I logged in with
test
User and opened the user browse screen. Only the admin user is shown:
-
I linked
userRole
toOnlyCreatedByUser
Access Group:
-
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. -
I removed the link
userRole
-OnlyAdminUser
, logged in again withtest
User and opened the user browse screen. Now, only theuserCreatedByTest
User is show!!
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
Regards,
Peterson.
This is ultimately how I solved this. I used .withInMemory
constraints that use custom Predicates to handle what I needed.