How do i use the mask field in acl_entry table in Spring Security 3.1?
Asked Answered
U

4

6

I use the Spring Security 3.1 ACL implementation. So based on a tutorial i have created a acl databse with the following tables:

CREATE TABLE IF NOT EXISTS `acl_class` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `class` varchar(255) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `unique_uk_2` (`class`)
) ENGINE=InnoDB;

CREATE TABLE IF NOT EXISTS `acl_entry` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `acl_object_identity` bigint(20) NOT NULL,
  `ace_order` int(11) NOT NULL,
  `sid` bigint(20) NOT NULL,
  `mask` int(11) NOT NULL,
  `granting` tinyint(1) NOT NULL,
  `audit_success` tinyint(1) NOT NULL,
  `audit_failure` tinyint(1) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `unique_uk_4` (`acl_object_identity`,`ace_order`),
  KEY `foreign_fk_5` (`sid`)
) ENGINE=InnoDB;

CREATE TABLE IF NOT EXISTS `acl_object_identity` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `object_id_class` bigint(20) NOT NULL,
  `object_id_identity` bigint(20) NOT NULL,
  `parent_object` bigint(20) DEFAULT NULL,
  `owner_sid` bigint(20) DEFAULT NULL,
  `entries_inheriting` tinyint(1) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `unique_uk_3` (`object_id_class`,`object_id_identity`),
  KEY `foreign_fk_1` (`parent_object`),
  KEY `foreign_fk_3` (`owner_sid`)
) ENGINE=InnoDB;

CREATE TABLE IF NOT EXISTS `acl_sid` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `principal` tinyint(1) NOT NULL,
  `sid` varchar(100) NOT NULL,
  `password` varchar(255) NOT NULL,
  `salt` varchar(255) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB;

This works fine with Anntotations like:

@PreAuthorize("hasPermission(#element, 'WRITE')")
@PostAuthorize("hasPermission(returnObject, 'READ')")

The rights "Read" and "Write" are set in the table acl_entry to the field mask. As i understood 1 means "READ", 2 means "Write", 4 means "Create", 8 means "Delete" and 16 means "Administer", because it seems to be a bitwise authentication method.

  1. Question: Do i have correctly understood the granting of rights?
  2. Question: How do i specify combined rights like "Read/Write"? Can i "set" the bit 0 (which is int 1) and 1 (which is int 2) so i get the mask value 1+2=3?

Now i have to create single entries for the "READ" and "Write" permission, that's not pretty handy.

Unpeople answered 26/1, 2012 at 12:43 Comment(0)
T
6

According to Spring Security 3.1 by PacktPub:

Unfortunately, the actual implementation of AclImpl directly compares the permission specified in our SpEL expression in our [@PostFilter] annotation, and the permission stored on the ACE in the database, without using bitwise logic. The Spring Security community is in debate about whether this is unintentional or working as intended. . .

The example in that book tries to do exactly what you're describing -- it specifies a user with a role of 3 for read/write, but the user is denied access to an object with a permission mask of 1 for read.

The solution is to write your own custom permission evaluator.

MyPermissionEvaluator.java:

public class MyPermissionEvaluator implements PermissionEvaluator {

    @Override
    public boolean hasPermission(Authentication authentication, Object targetDomainObject, Object requiredPermissions) {
        //some way to access your user's assigned permission mask
        int permissionMask = MyUserDetails.getMask();

        //the requiredPermissions object must be cast as a String, and then
        //converted to an integer, even though it is an integer in the ACL table
        int permissionsRequired = Integer.valueOf(requiredPermissions.toString());

        //a simple bitwise OR will identify whether the user has the required permissions
        return ((permissionMask | permissionsRequired) == permissionMask);
    }

    . . .

}

To actually use this custom permission evaluator, edit your security.xml file:

<security:global-method-security pre-post-annotations="enabled">
    <security:expression-handler ref="expressionHandler"/>
</security:global-method-security>

<bean id="espressionHandler" class="org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler">
    <property name="permissionEvaluator" ref="permissionEvaluator"/>
</bean>

<bean id="permissionEvaluator" class="my.project.package.MyPermissionEvaluator"/>

Finally, whenever a method or class requires a certain permission level:

@PreAuthorize("hasPermission(#this, '4')")
public void mySecuredMethod() { //some secured method
}

Now you can set the permission mask in the ACL table to whatever corresponds to your organizational needs (bitwise), and do the same in whatever way you identify each individual user's permissions. For example,

user    site_admin_bit    database_admin_bit    edit_data_bit    write_data_bit    read_data_bit
nancy        0                   1                   1                 0                1

Nancy thus has a permission mask of 13 (out of a possible 31) as stored in your user details implementation. If she tries to access an object with a permission requirement of edit_data, her permissions would be checked against a mask requirement of 4, and a bitwise OR evaluation (permissionMask | permissionsRequired == permissionMask) would evaluate to true.

This is, in my estimation, the easiest way to implement an organization-specific bitwise permissions mask (with 32 bits to play with, which should be enough, I should think). According to the referenced book, the hasPermission SpEL expression used in Spring annotations evaluates the user's permissions as a complete unit; if the user has a permission set at 3 for read/write, but the annotation only evaluates against read (1), the user will be denied access.

Teide answered 23/4, 2013 at 16:38 Comment(2)
Pretty sure this needs to be a bitwise AND not a bitwise OR. So it should be return ((permissionMask & permissionsRequired) == permissionMask);Subarctic
FYI. The first link is now a 404Approve
C
2

In order to implement a bit-wise permission evaluation, instead of implementing a PermissionEvaluator, which can be quite difficult, you can override the DefaultPermissionGrantingStrategy with your own one.

This could be your Spring ACL configuration

1) Your ACL Service

<bean class="org.springframework.security.acls.jdbc.JdbcMutableAclService" id="aclService">
<constructor-arg ref="dataSource"/>
<constructor-arg ref="lookupStrategy"/>
<constructor-arg ref="aclCache"/>
</bean>

2) Your lookup strategy (dataSource, aclCache and aclAuthorizationStrategy are the default ones)

<bean id="lookupStrategy" class="org.springframework.security.acls.jdbc.BasicLookupStrategy">
<constructor-arg ref="dataSource"/>
<constructor-arg ref="aclCache"/>
<constructor-arg ref="aclAuthorizationStrategy"/>
<constructor-arg ref="permissionGrantingStrategy"/>
</bean>

3) Here comes the interesting part, the PermissionGrantingStrategy (http://docs.spring.io/spring-security/site/docs/current/apidocs/org/springframework/security/acls/model/PermissionGrantingStrategy.html)

<bean id="permissionGrantingStrategy" class="com.example.MyPermissionGrantingStrategy"/>

Here is where you would implement the bit-wise permission logic, and then override the Spring's default one.

I hope this can help

Coastal answered 6/11, 2014 at 12:51 Comment(2)
FYI. The Spring doc link is brokenApprove
Thank you. I couldn't work out why my custom permission granting strategy wasn't being used until I saw this. I had only set the permission granting strategy on the aclCache but not on the lookupStrategy bean.Cuttlefish
K
1

From SpringSecurity

As mentioned in the last paragraph, the ACL system uses integer bit masking. Don't worry, you need not be aware of the finer points of bit shifting to use the ACL system, but suffice to say that we have 32 bits we can switch on or off. Each of these bits represents a permission, and by default the permissions are read (bit 0), write (bit 1), create (bit 2), delete (bit 3) and administer (bit 4). It's easy to implement your own Permission instance if you wish to use other permissions, and the remainder of the ACL framework will operate without knowledge of your extensions.

@question#1: yes that's right.

@question#2: you could use something like: new BasePermission(BasePermission.WRITE.getMask() | BasePermission.READ.getMask()) to get a READ and WRITE permission.

From the spring docs:

// Prepare the information we'd like in our access control entry (ACE)
ObjectIdentity oi = new ObjectIdentityImpl(Foo.class, new Long(44));
Sid sid = new PrincipalSid("Samantha");
//Permission p = BasePermission.ADMINISTRATION;
Permission p = new BasePermission(BasePermission.WRITE.getMask() | BasePermission.READ.getMask());    

// Create or update the relevant ACL
MutableAcl acl = null;
try {
  acl = (MutableAcl) aclService.readAclById(oi);
} catch (NotFoundException nfe) {
  acl = aclService.createAcl(oi);
}

// Now grant some permissions via an access control entry (ACE)
acl.insertAce(acl.getEntries().length, p, sid, true);
aclService.updateAcl(acl);
Kern answered 26/1, 2012 at 16:23 Comment(3)
Okay, the new BasePermission is a solution, but i need 2 entries in the database table acl_entry. I'd like to save in database a mask value 3, so Spring Security would give true for @PreAuthorize("hasPermission(#element, 'WRITE')") and @PostAuthorize("hasPermission(returnObject, 'READ')"). Is that possible?Unpeople
I have added a code sample, copied from the spring docs. It will do exactly what you need. Take a look at the Permission object in the code sample. You should end up with exactly 1 row in the database for the read/write permission.Kern
I have tried the new BasePermission. It works fine if i want to check for "READ/WRITE", but not for single permissions, like i expected. So @PreAuthorize("hasPermission(#element, 'READ/WRITE')") is true but @PreAuthorize("hasPermission(#element, 'WRITE')") is false. So it is not really what i wanted to do, but still helpful. ThanksUnpeople
A
1

This answer pointed out that Spring compares for an exact match between the ACE mask and the permission we're checking. As the OP says:

Now i have to create single entries for the "READ" and "Write" permission, that's not pretty handy.

-Yes. This seems like the default behaviour. As we can see from any of the DB schemas presented here, we are allowed to add multiple entries for a given object. So if we want to grant Alice read and write access, we'd add an entry for read, maybe with order 1, and then an entry for write, with order 2 or something.

Although the default behaviour seems to be as above, the mention of "bitwise" in the documentation is probably confusing newcomers. However, I'd imagine that for backwards compatibility of a framework that's been around for so long and relied upon by so many, it's probably too late to change the default behaviour. The Spring developers seem to be well aware of this conundrum, and so they have provided a simple enough way to overcome it. As this answer points out, the key is to provide a custom PermissionGrantingStrategy. The linked answer shows how to do hook in the class via XML. I am more comfortable with code based configuration though, so I followed this link which explains how to do set up Spring ACL in code.

Now, all you have to do that's different to what's described in that link, is to replace this Bean definition:

@Bean
public PermissionGrantingStrategy permissionGrantingStrategy() {
    return new DefaultPermissionGrantingStrategy(
      new ConsoleAuditLogger());
}

And instead return a permission granting strategy of your own making. Don't worry though! You don't have to write your own class completely from scratch. Your custom permission granting strategy can then just subtype the class DefaultPermissionGrantingStrategy and override only the method isGranted. The default implementation indeed just does an exact match:

protected boolean isGranted(AccessControlEntry ace, Permission p) {
    return ace.getPermission().getMask() == p.getMask();
}

But the author seems well aware of the need for an override, based on this snippet from the method's doc:

By default, we compare the Permission masks for exact match. Subclasses of this strategy can override this behavior and implement more sophisticated comparisons, e.g. a bitwise comparison for ACEs that grant access.

The documentation goes even further to actually give you the code to do the override. I haven't tested this, but I believe it's just:

protected boolean isGranted(AccessControlEntry ace, Permission p) {
        if (ace.isGranting() && p.getMask() != 0) {
            return (ace.getPermission().getMask() & p.getMask()) != 0;
         } else {
            return ace.getPermission().getMask() == p.getMask();
         }
}

Should you Use Bitwise ACL?

Before overriding Spring ACL's default to use bitwise storage, you should first ask yourself is it worth the extra effort and the risk of bugs by deviating from OOTB behaviour.

But also, you should be aware that you will lose some behaviour that is available in non-bitwise Spring ACL: namely the ability to delete ACEs. I'll demonstrate with an example. Suppose there is a Company object called C and a Department object called D which is nested underneath C in the Spring ACL hierarchy. And suppose D inherits its permissions from C.

Now suppose Alice has READ access to the entire company C. From this, she inherits READ access to all departments underneath C, including D. One day, Bob decides he wants to grant Alice WRITE access to the department D only, but not any other departments. With bitwise logic, what ACE gets added for D? Since the single ACE must include all permissions READ, WRITE etc. encoded into the various bits, the READ bit must be either 0 or 1. Both are potentially problematic:

  1. Suppose the READ bit on the new ACE is 0. It would be crazy to assume Alice is now denied READ access to D, so we'd have to assume that a 0 bit doesn't mean "denied" but the weaker meaning "not granted". Meaning the ACL logic will still look to parent objects to grant the permission. This means that we lose the ability to DENY-override permissions that were granted at a higher level in the hierarchy.
  2. On the other hand, we could implement a more complex logic which detects that Alice has READ access via the parent object C and so sets the READ bit to 1 on the new ACE. But then what happens if we remove Alice's READ access to C? There is no way to know that the READ access to D should also be removed, unless we store that info somewhere, and doing so would defeat the whole point of bitwise ACLs which is to compress storage.

One way around this would be to use two bits for every permission, instead of 1. For example, you could interpret as follows: 00 means not granted or denied - look to parents for a value, 01 means denied, 10 means granted. 11 would be free in that case, but you might use it for something else later, for example. However, doing it with 2 bits would be more complex because the OOTB permissions are only 1 bit, so you'd have to write your own. And for all that effort, you'd only get half the storage compression at the end of it.

Approve answered 23/6, 2021 at 18:18 Comment(1)
I guess for users who aren't making use of the parent child relationship within spring ACL (perhaps as in my case due to a many to many relationship between parent and child entities), then the bitwise permission strategy would make sense, correct?Cuttlefish

© 2022 - 2024 — McMap. All rights reserved.