Announcement Announcement Module
Collapse
No announcement yet.
Making Acegi ACLs and Hibernate work together nicely... Page Title Module
Move Remove Collapse
This topic is closed
X
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • Making Acegi ACLs and Hibernate work together nicely...

    I'm using Hibernate, and have recently started using Acegi ACLs. I'm running into an annoying problem that I hope someone else has had experience with:

    1) I use Hibernate xmls to create the two tables acl_object_identity and acl_permission

    2) Hibernate also creates the POJOs for these tables, AclObjectIdentity and AclPermission

    So here's the problem. Acegi comes with all sorts of nice ACL classes, such as JdbcDaoImpl, but all of these use the Acegi class structure with BasicAclEntry, etc. The problem is, my POJOs do not easily fit into this structure, and I am ending up with two parallel class hierarchies for saving into the acl_object_identity and acl_permission table (using the Hibernate POJOs) and my app, otherwise.

    So here are two thoughts:

    1) Has anyone thought about writing the "official" ACL classes to support the Hibernate POJOs, which I can point to in the mappings?

    2) Are there any other ways to easily integrate the Acegi ACL support and Hibernate, so I don't have these two sets of classes? In particular, the JdbcDaoImpl are very not-Hibernate, since they require you to embed SQL queries in variables, etc.

    Any ideas on this would be appreciated.

    Andrew

  • #2
    Hey There,

    I wanted to clarify your quote:

    The problem is, my POJOs do not easily fit into this structure, and I am ending up with two parallel class hierarchies for saving into the acl_object_identity and acl_permission table (using the Hibernate POJOs) and my app, otherwise.
    I am working on an implementation where I use Hibernate persistence, but am content to use the JDBC out-of-the-box implementation for the ACL stuff. However, you might be a bit ahead of me as I've only wired in the JDBC class instances into my context.

    My question was going to be what are you doing with your POJOs that causes the dual hierarchy? If you are using the AclObjectIdentity interface then you do actually have identity "shadows" for the instances that you wish to secure. And as has been said in a previous post, its actually quite nice to separate your domain relationships and hierarchies from your security concerns.

    In my case, I have a catalog which has categories which has items. I've chosen to have my security approach only deal with the whole catalog and then items themselves. As such, I have AclObjectIdentity instances for the catalog and for every item. And each item has the catalog as a parent. As you will note, this bypasses the categories which have nothing to do (in my application) with how access rights might be assigned.

    I'm not sure if my example helps or if it distracts from your original question. Perhaps if you could post more about your POJOs and your approach to security within your domain.

    If you wish to use the Basic ACL implementation, you will have to keep in mind that your identity hierarchy needs to use single inheritance (an instance can have only one parent). I think the confusion sometimes comes in where people think that their domain structure must map one-to-one to the security hierarchy. It doesn't appear that this is always the case.

    I hope this helps in some small way...

    Bill

    Comment


    • #3
      Recall just about every layer of Acegi Security is replacable. In most cases you want to replace as little as possible, as it means less work. :-)

      In the case of ACL persistence, I would suggest writing Hibernate mappings for the classes is an unnecessary complication. The JdbcExtendedDaoImpl provides full CRUD operations for the ACL domain objects, so I can't see the value-add of re-writing them to use Hibernate just for the sake of it.

      Now, if on the other hand you decided to do some work with Hibernate, I'd suggest you implement BasicAclExtendedDao and consider providing Hibernate-specific subclass of AbstractBasicAclEntry. AbstractBasicAclEntry is the main class supported by most of Acegi Security ACL-related classes, so it is the one to subclass (not BasicAclEntry or SimpleAclEntry).

      If you were doing a Hibernate implementation, you probably should consider normalising the schema a bit better.

      Any Hibernate contributions are welcome.

      Comment


      • #4
        Hibernate implementation

        Has there been any developments for converting the Jdbc* classes to use Hibernate? It really makes life easy when trying to do development on HSQL and deploy onto Oracle because of just the dialect change.

        Comment


        • #5
          No, but we'd welcome a Hibernate contribution, particularly if it also had unit tests. :-)

          Comment


          • #6
            Hibernate implementation

            I have just created the hibernate version for BasicAclExtendedDao. I will see about bundling it in a zip in a couple of weeks. I am also going to create one for the User Dao. Is it Ok if I just post the source code? Do I have to have a working package?

            Comment


            • #7
              Another big problem when using Hibernate and ACEGI is the generation of the NamedEntityObjectIdentity.
              Hibernate generates proxied classes, and if this is the case (e.g. when modifying an lazy loaded object), <object>.getClass().getName() results in a value .....$$$EnhancerByCGLib$$$...., and so ACEGI fails finding the right ACLObjectIdentity entry -> Access Exception.

              I know that this is not an ACEGI problem, but maybe there is an easy way to replace the NamedObjectIdentity when creating the ACEGI Beans in Spring...

              Comment


              • #8
                Actually, I don't have that problem as it won't create a proxy for a domain class unless you specify the lazy=true on the main class. It will create proxies for Sets that are set to lazy-load. My security framework works great with hibernate on both my domain model and the security.

                Comment


                • #9
                  As far as I know, hibernate tries to build proxies for all objects since Hibernate 3. You can add lazy="false" to the class tag, but these will result in heavy database traffic...

                  I changed some code in the latest CVS snapshot of ACEGI, and it works great:

                  Class net.sf.acegisecurity.acl.basic.NamedEntityObjectId entity:


                  Code:
                  package net.sf.acegisecurity.acl.basic;
                  
                  import org.springframework.util.Assert;
                  
                  import java.lang.reflect.InvocationTargetException;
                  import java.lang.reflect.Method;
                  
                  
                  /**
                   * Simple implementation of &#123;@link AclObjectIdentity&#125;.
                   * 
                   * <P>
                   * Uses <code>String</code>s to store the identity of the domain object
                   * instance. Also offers a constructor that uses reflection to build the
                   * identity information.
                   * </p>
                   *
                   * @author Ben Alex
                   * @version $Id&#58; NamedEntityObjectIdentity.java,v 1.4 2005/05/09 01&#58;18&#58;29 benalex Exp $
                   */
                  public class NamedEntityObjectIdentity implements AclObjectIdentity 
                  &#123;
                      //Added by PSM &#40;see http&#58;//cglib.sourceforge.net/apidocs/net/sf/cglib/core/DefaultNamingPolicy.html&#41;
                  	private static final String CGLIB_NAMING_POLICY = "$$EnhancerByCGLIB$$";
                  	//~ Instance fields ========================================================
                  
                      private String classname;
                      private String id;
                  
                      //~ Constructors ===========================================================
                  
                      public NamedEntityObjectIdentity&#40;String classname, String id&#41; &#123;
                          if &#40;&#40;classname == null&#41; || "".equals&#40;classname&#41;&#41; &#123;
                              throw new IllegalArgumentException&#40;"classname required"&#41;;
                          &#125;
                  
                          if &#40;&#40;id == null&#41; || "".equals&#40;id&#41;&#41; &#123;
                              throw new IllegalArgumentException&#40;"id required"&#41;;
                          &#125;
                          //Changed by PSM
                          this.classname = getClassNameWithOutCGLib&#40;classname&#41;;
                          this.id = id;
                      &#125;
                  
                      /**
                       * Creates the <code>NamedEntityObjectIdentity</code> based on the passed
                       * object instance. The passed object must provide a <code>getId&#40;&#41;</code>
                       * method, otherwise an exception will be thrown.
                       *
                       * @param object the domain object instance to create an identity for
                       *
                       * @throws IllegalAccessException
                       * @throws InvocationTargetException
                       * @throws IllegalArgumentException
                       */
                      public NamedEntityObjectIdentity&#40;Object object&#41;
                          throws IllegalAccessException, InvocationTargetException &#123;
                          Assert.notNull&#40;object, "object cannot be null"&#41;;
                  
                          //Changed by PSM
                          this.classname = getClassNameWithOutCGLib&#40;object.getClass&#40;&#41;.getName&#40;&#41;&#41;;
                  
                          Class clazz = object.getClass&#40;&#41;;
                  
                          try &#123;
                              Method method = clazz.getMethod&#40;"getId", new Class&#91;&#93; &#123;&#125;&#41;;
                              Object result = method.invoke&#40;object, new Object&#91;&#93; &#123;&#125;&#41;;
                              this.id = result.toString&#40;&#41;;
                          &#125; catch &#40;NoSuchMethodException nsme&#41; &#123;
                              throw new IllegalArgumentException&#40;"Object of class '" + clazz
                                  + "' does not provide the required getId&#40;&#41; method&#58; " + object&#41;;
                          &#125;
                      &#125;
                  
                      
                  
                  	protected NamedEntityObjectIdentity&#40;&#41; &#123;
                          throw new IllegalArgumentException&#40;"Cannot use default constructor"&#41;;
                      &#125;
                  
                      //~ Methods ================================================================
                  
                      /**
                       * Indicates the classname portion of the object identity.
                       *
                       * @return the classname &#40;never <code>null</code>&#41;
                       */
                      public String getClassname&#40;&#41; &#123;
                          return classname;
                      &#125;
                  
                      /**
                       * Indicates the instance identity portion of the object identity.
                       *
                       * @return the instance identity &#40;never <code>null</code>&#41;
                       */
                      public String getId&#40;&#41; &#123;
                          return id;
                      &#125;
                  
                      /**
                       * Important so caching operates properly.
                       * 
                       * <P>
                       * Considers an object of the same class equal if it has the same
                       * <code>classname</code> and <code>id</code> properties.
                       * </p>
                       *
                       * @param arg0 object to compare
                       *
                       * @return <code>true</code> if the presented object matches this object
                       */
                      public boolean equals&#40;Object arg0&#41; &#123;
                          if &#40;arg0 == null&#41; &#123;
                              return false;
                          &#125;
                  
                          if &#40;!&#40;arg0 instanceof NamedEntityObjectIdentity&#41;&#41; &#123;
                              return false;
                          &#125;
                  
                          NamedEntityObjectIdentity other = &#40;NamedEntityObjectIdentity&#41; arg0;
                  
                          if &#40;this.getId&#40;&#41;.equals&#40;other.getId&#40;&#41;&#41;
                              && this.getClassname&#40;&#41;.equals&#40;other.getClassname&#40;&#41;&#41;&#41; &#123;
                              return true;
                          &#125;
                  
                          return false;
                      &#125;
                  
                      /**
                       * Important so caching operates properly.
                       *
                       * @return the hash of the classname and id
                       */
                      public int hashCode&#40;&#41; &#123;
                          StringBuffer sb = new StringBuffer&#40;&#41;;
                          sb.append&#40;this.classname&#41;.append&#40;this.id&#41;;
                  
                          return sb.toString&#40;&#41;.hashCode&#40;&#41;;
                      &#125;
                  
                      public String toString&#40;&#41; &#123;
                          StringBuffer sb = new StringBuffer&#40;&#41;;
                          sb.append&#40;this.getClass&#40;&#41;.getName&#40;&#41;&#41;.append&#40;"&#91;"&#41;;
                          sb.append&#40;"Classname&#58; "&#41;.append&#40;this.classname&#41;;
                          sb.append&#40;"; Identity&#58; "&#41;.append&#40;this.id&#41;.append&#40;"&#93;"&#41;;
                  
                          return sb.toString&#40;&#41;;
                      &#125;
                      /**
                       * Added by PSM
                       * Returns the classname without the CGLIB name extension
                       * @param name The className to be cleared
                       * @return The real className
                       */
                      private String getClassNameWithOutCGLib&#40;String name&#41; 
                      &#123;
                  		String realClassName = name;
                  		if&#40;name.indexOf&#40;CGLIB_NAMING_POLICY&#41;!=-1&#41;
                  		&#123;
                  			int endIndex = name.indexOf&#40;CGLIB_NAMING_POLICY&#41;;
                  			realClassName = name.substring&#40;0,endIndex&#41;; 
                  		&#125;
                  		return realClassName;
                  	&#125;
                  &#125;

                  Class net.sf.acegisecurity.acl.basic.NamedEntityObjectId entityTests

                  And a new test method:

                  Code:
                  public void testCleanEnhancedClasses&#40;&#41;
                      &#123;
                      	NamedEntityObjectIdentity name = new NamedEntityObjectIdentity&#40;"net.sf.cglib.Foo$$EnhancerByCGLIB$$38272841","id"&#41;;
                      	assertEquals&#40;"net.sf.cglib.Foo", name.getClassname&#40;&#41;&#41;;
                          assertEquals&#40;"id", name.getId&#40;&#41;&#41;;
                      &#125;

                  Comment


                  • #10
                    But it still falls down for discriminated entities...

                    Hi,

                    I've spent most of today wrestling a related issue, so I thought I'd post the solution here so others might use it.

                    An example of the problem I faced is this: given that entity com.acme.Foo extends com.acme.Bar and that Foo and Bar are discriminated entities, Hibernate will return a proxied Foo object whose getClass().getName() is something like "com.acme.Bar$$$EnhancerByCGLib239847".

                    Hibernate gives us a static method getClass(Object) that knows what to do with these strange objects: in this example Hibernate.getClass(proxiedObject) will return "com.acme.Foo".

                    The problem this causes for Acegi is that ACL entries for a particular Foo object are stored in the ACL_OBJECT_IDENTITY table with object_identity values like "com.acme.Foo#123", whereas the wierd getClass().getName() behaviour of the proxied Foo object cause ACL lookups on "com.acme.Bar#123", which fail of course.

                    The end result for our application was a sporadic failure of authorisation logic.

                    I fixed this by extending NamedEntityObjectIdentity with this class:

                    Code:
                    public class CGLIBFixNamedEntityObjectIdentity extends NamedEntityObjectIdentity {
                        private String classname;
                        private String id;
                    
                        
                        public CGLIBFixNamedEntityObjectIdentity(String classname, String id) {
                            super(classname, id);
                        }
                    
                        public CGLIBFixNamedEntityObjectIdentity(Object object) throws IllegalAccessException, InvocationTargetException {
                            super(object);
                            Assert.notNull(object, "object cannot be null");
                    
                            this.classname = (getPackageName(Hibernate.getClass(object).getName()) == null)
                                ? ClassUtils.getShortName(Hibernate.getClass(object))
                                : (getPackageName(Hibernate.getClass(object).getName()) + "." + ClassUtils.getShortName(Hibernate.getClass(object)));
                    
                            Class clazz = Hibernate.getClass(object);
                    
                            try {
                                Method method = clazz.getMethod("getId", new Class[] {});
                                Object result = method.invoke(object, new Object[] {});
                                this.id = result.toString();
                            } catch (NoSuchMethodException nsme) {
                                throw new IllegalArgumentException("Object of class '" + clazz
                                    + "' does not provide the required getId() method: " + object);
                            }
                        }
                    
                        private String getPackageName(String className) {
                            Assert.hasLength(className, "class name must not be empty");
                    
                            int lastDotIndex = className.lastIndexOf(".");
                    
                            if (lastDotIndex == -1) {
                                return null;
                            }
                    
                            return className.substring(0, lastDotIndex);
                        }
                    
                    
                        public String getClassname() {
                            return classname;
                        }
                    }
                    You can plug in this class by specifying it as the 'defaultAclObjectIdentityClass' property of the BasicAclProvider. In our case this just involved adding the following line to our acegi-security.xml inside the 'basicAclProvider' bean definition:

                    Code:
                    <property name="defaultAclObjectIdentityClass" value="gov.qiris.acegisecurity.acl.CGLIBFixNamedEntityObjectIdentity"/>
                    The fact that Hibernate provide the Hibernate.getClass(Object) method makes me think that they regard this behaviour as a 'feature' rather than a bug, so this workaround class may always be needed, even with future versions of Hibernate.

                    Comment

                    Working...
                    X