Suresh Payankannur

Showing posts with label hibernate. Show all posts
Showing posts with label hibernate. Show all posts

Tuesday, October 22, 2013

Hibernate Row Level Security using Apache Shiro

Apache Shiro is a feature rich and simpler security framework that can be easily integrated with any enterprise level systems. It comes with a default implementation of permissions and it is much easier to implement any custom security framework on top of it. The features offered by Shiro are good enough for most of the security requirements.

This posting describes a simple way to implement row level security using Hibernate ORM and Apache Shiro in a multi-tenant environment.

Design Approach


  1. Each entity in the model that requires row level security should have a tenant id as part of the entity.
  2. Define the user level permissions using the Shiro permission syntax: entity-type:operation:instance-id
  3. Use hibernate filters to add additional predicates to the queries.
  4. Set the values of hibernate filters using the currently authenticated users permissions.

Notes


  1. Shiro do not have direct way of obtaining the current roles or permissions for the current subject. So this information is obtained using the Realm interface.
  2. Use caching to improve performance. Shiro supports out of the box support caching. Simplest way is to use EHCache.

The Model


The model contains two entities. A Company and a list of Employees. An authenticated user can only see Employees for the Company he/she has granted permission to.
@MappedSuperclass
@FilterDef(name = "rlsFilter",
           defaultCondition = "fk_company_id in (:companies)",
           parameters = {@ParamDef(name="companies", type="long")})
@Filter(name = "rlsFilter")
public abstract class SecuredEntity {
    @ManyToOne
    @JoinColumn(name = "fk_company_id", nullable=false)
    public Company getCompany() {
        return this.company;
    }
    public void setCompany(Company c) {
        this.company = c;
    }
    private Company company;
}

@Entity
@Table(name = "employee")
public class Employee extends SecuredEntity {
    @Id
    @GeneratedValue
    public long getId() {
        return this.id;
    }
    public void setId(long newId) {
        this.id = newId;
    }
    @Column(name = "email")
    public String getEmail() {
        return this.email;
    }
    public void setEmail(String neEmail) {
        this.email = newEmail;
    }
    @Column(name = "full_name")
    public String getFullname() {
        return this.fullname;
    }
    public void setFullname(String name) {
        this.fullname = name;
    }

    private long id;
    private String email;
    private String fullname;
}

@Entity
@Table(name = "company")
public class Company {
    @Id
    @GeneratedValue
    public long getId() {
        return this.id;
    }
    public void setId(long newId) {
        this.id = newId;
    }
    public String getName() {
        return this.name;
    }
    public void setName(String nm) {
        this.name = nm;
    }
    private long id;
    private String name;
} 

Permission Model


The permission model consists of a User representing a user in the system and a set of Permissions. The Permissions will follow the Shiro StringPermission syntax: resource-type:operation:instance-id. For example, company:*:1234 means permission for company with id 1234 for all operations.
@Entity
@Table(name = "user")
public class User {
    @Id
    @GeneratedValue
    public long getId() {
        return this.id;
    }
    public void setId(long newId) {
        this.id = newId;
    }
    @Column(name = "user_name")
    public String getUsername() {
        return this.username;
    }
    public void setUsername(String nm) {
        this.username = nm;
    }
    @Column(name = "user_pass")
    public String getPassword() {
        return this.password;
    }
    public void setPassword(String pwd) {
        this.password = pwd;
    }
    @ManyToMany
    @JoinTable(name = "user_permission",
               joinColumns = @JoinColumn(name = "fk_user_id"),
               inverseJoinColumn(name = "fk_permission_id"))
    public Set<Permission> getPermissions() {
        return this.permissions;
    }
    public void setPermission(Set<Permission> perms) {
        this.permissions = perms;
    }
    private long id;
    private String username;
    private String password;
    private Set<Permission> permissions = new HashSet<String>();
}

@Entity
@Table(name = "permission")
public class Permission {
    @Id
    @GeneratedValue
    public long getId() {
        return this.id;
    }
    public void setId(long newId) {
        this.id = newId;
    }
    public String getValue() {
        return this.value;
    }
    public void setValue(String val) {
        this.value = val;
    }
    private long id;
    private String value;
}

Shiro Plumbing


  1. Create a custom Realm class. Implement the authentication and authorization methods based on the permission model.
  2. Get the permissions for the currently logged in user
public class MyShiroRealm extends AuthorizingRealm {
    public boolean supports(AuthenticationToken token) {
        return token ! null
            &&  UsernamePasswordToken.class.isAssignableFrom(token.getClass());
    }
    public AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        User user = (User) principals.getPrimaryPrincipal();

        SimpleAuthorizationInfo auth = new SimpleAuthorizationInfo();

        for (Permisison p : user.getPermissions()) {
            auth.addStringPermission(p.getPermission());
        }
    }
    public AuthenticationInfo getAuthenticationInfo(AuthenticationToke token) {
        UsernamePasswordToken authToken = (UsernamePasswordToken) token;

        User user = userDao.getUser(authToken.getUsername());
        if (user != null) {
            auth = new SimpleAuthenticationInfo(user, user.getPassword(),
                                                realmName);
        } else {
            throw new AccountException("Account not found for user: "
                                       + authToken.getUsername())
        }
        return auth;
    }
    public Collection<String> getPermissions() {
        Subject subject = SecurityUtils.getSubject();
        AuthorizationInfo auth = getAuthorizationInfo(subject.getPrincipals());

        return auth != null
            ?  auth.getStringPermissions()
            :  new ArrayList<String>();
    }

    private String realmName = "MyShiroRealm";
}

Hibernate Integration


Hibernate layer integrates the Shiro permissions using filters as explained above. This can be done when a new session is obtained. If you are using OpenSessionInViewFilter, then subclass it and enable the filter soon after the session is opened.

  1. To provide individual permissions, follow the syntax: company:*:123
  2. To provide permissions to all companies, define permission string as: company:*
  3. If the user has permission to ALL companies, then the filter will be disabled
  4. If user has no company permission, then the filter will be executed with a bogus company id of -2.
public class EmployeeDao {
    protected Session getSession() {
        Session session = sessionFactory.getCurrentSession();

        Collecton<Long> companies = getAuhorizedCompanies();
        if (!companies.isEmpty()) {
            if (companies.size() == 1
                &&  companies.iterator().next() == ALL_COMPANIES) {

                session.disableFilter("rlsFilter");
            } else {
                Filter filter = sesson.enableFilter("rlsFilter");
                filter.setParameterList("companies", companies);
            }
        } else {
            companies.add(NO_COMPANIES);
            Filter filter = sesson.enableFilter("rlsFilter");
            filter.setParameterList("companies", companies);
        }
        return session;
    }
    protected List<Long> getAuthorizedCompanies() {
        List<Long> companies = new ArrayList<Long>();

        List<String> permissions = getPermissions();
        for (String p : permissions) {
            String[] parts = s.split(":");

            if (parts != null  &&  parts.length > 0) {
                if (parts[0].equalsIgnoreCase("company")) {
                    if (parts.length == 3) {
                        if (parts[2].equals("*")) {
                            companies.add(new Long(ALL_COMPANIES));
                            break;

                        } else {
                            companies.add(Long.parseLong(parts[2]));
                        }

                    } else if (parts.length < 3) {
                        companies.add(new Long(ALL_COMPANIES));
                        break;
                    }
                }
            }
        }
        return companies;
    }
    protected List<String> getPermissions() {
        List<String> permissions = new ArrayList<String>();

        RealmSecurityManager mgr =
            (RealmSecurityManager) SecurityUtils.getSecurityManager();

        if (mgr != null) {
            for (Realm r : mgr.getRealms()) {
                Collection<String> list = ((MyShiroRealm) r).getPermissions();

                if (list != null) {
                    permisisons.addAll(list);
                }
            }
        }
        return permissions;
    }

    public List<Employee> getAllEmployees() {
        Session session = getSession();
        // do the hibernate query
    }
    private SessionFactory sessionFactory;

    private static final long ALL_COMPANIES = -1;
    private static final long NO_COMPANIES  = -2;
}

Testing


  1. Create some users
  2. Assign some permissions
  3. Execute a hibernate based query (eg: getAllEmployees)
  4. Watch the SQL query executed and see the in clause predicate

Tuesday, April 10, 2012

MySQL and Spring Transactional Tests

For years, I have been using the Transaction support provided by the Spring test framework to write transaction aware unit tests. In the early days, it was by extending AbstractTransactionalSpringContextTests, which was replaced later with the introduction of Test Contexts. Now a days, just by annotating the class with TransactionConfiguration will trigger the transaction listeners to manage the class level or method level transactions. Where @Transaction annotation is used either at the class or method level.

Recently I was doing a new project using JPA, Hibernate, Spring and MySQL and building a generic unit test framework for it. The standard way I have been configuring MySQL with Hibernate was by using org.hibernate.dialect.MySQLDialect. But this was giving strange problems when running transactional unit test cases. The @Rollback(true) annotation was not working at all. At the end of the tests case, what ever database changes are made, they were committed to the database, as if the Rollback annotation do not have any effect at all. I was creating the schema, including tables using Hibernate's schema script creation APIs.

After searching for a solution, finally I found that in the later versions of Hibernate, there are additional dialects for MySQL. There are two more dialects added, MySQLInnoDBDialect and MySQL5InnoDBDialect. So the old usage with org.hibernate.dialect.MySQLDialect will create ISAM tables, which are not transactional. When the dialect was switched to one of InnoDB dialects, the CREATE TABLE scripts emit additional ENGINE=InnoDB clause to the DDLs. And the transactions were rolled back properly at the end of the test case.

Spring Configuration


  

    
    
    
    
    
    
    
    
  

  
    

    
      
        TestModel
      
    

    
      
        ${testdb.hibernate.dialect}
      
    
  

  
    
  

Property File

jdbc.testdb.username     = user
jdbc.testdb.password     = pass
jdbc.testdb.url          = jdbc:mysql://localhost/testdb
jdbc.testdb.driver       = com.mysql.jdbc.Driver
testdb.hibernate.dialect = org.hibernate.dialect.MySQL5InnoDBDialect

Test Class

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations={"classpath:test-config.xml"})
@TransactionConfiguration(transactionManager="txManager", defaultRollback=false)
public class DaoTest {

    @Before
    @BeforeTransaction
    public void setup() {
        dbCreator.createSchema();
    }

    @Test
    @Transactional
    @Rollback(true)
    public void testSave() {
        TestModel model = createNewModel();

        dao.save(model);
    }

Spring & Hibernate 4

Recently I was trying to use Hibernate, JPA annotations and Spring Framework for a new project and trying to use H4 support in the org.springframework.orm.hibernate4 package. A couple of issues caused me some grief:

  • LocalSessionFactoryBean do not have any way to set a LobHandler as were in the previous hibernate3 version. Then I came across this comment from Juergen as part of https://jira.springsource.org/browse/SPR-9022

    We generally rely on the native Hibernate 4 ways of doing things more than before (with Hibernate 3).

    So for LOB access, we recommend native Hibernate types (e.g. org.hibernate.type.MaterializedClobType) instead of the former user types that came with Spring's Hibernate 3 support. With Hibernate 4 and modern JDBC drivers (e.g. Oracle 10g or even a JDBC 4 driver), you should not need Spring's LobHandler mechanism at all anymore.

    In short, please try to replace your Spring user types with native Hibernate CLOB types. See the Hibernate documentation for details on their support for CLOB access.

    Good info. I still need to test the data access code to ensure that the LOBs are correctly handled.

  • I have been setting up the Unit test cases to create/drop database schema using the create/drop DatabaseSchema APIs of the LocalSessionFactoryBean. But in H4, these APIs are removed. Need to find a new way to get this done, may be going directly using the Hibernate APIs. But it makes it a little inconvenient and requires extra framework to handle this in a generic way

Blog Archive

Scroll To Top