Hi all

I've recently submitted a bug about a problem I'm having with cglib
enhanced classes and Acegi acls. 

Seeing as the I've not seen a huge amount of attention paid to other
bugs I've submitted I thought I'd also post this to the mailing list,
because I've almost solved it, and I'm not sure how much of a problem
the unsolved bit is, and would like some opinions.

The bug is: http://opensource.atlassian.com/projects/spring/browse/SEC-602

And here's the low down, plus 2 attached files with a proposed (almost)
solution.

When using the AclEntryAfterInvocationProvider to check acl access on an
object which has been instantiated by (in my case) hibernate, it is
possible that hibernate will return a cglib enhanced instance of the
object (or it might not). When ObjectIdentityRetrievalStrategyImpl then
attempts to create an ObjectIdentity for it, that class name it gets is
something like "com.mycompany.Foo$$EnhancerByCGLIB$$beb2e4f5", which
does not match up with the class reference in the ObjectIdentity
database record which is "com.mycompany.Foo", and no corresponding ACL
entries will be found for the object.

I've found a couple of solutions for this, the first is to deproxy the
object immediately after the query using a method something like:

public static Object deproxy(Object obj)
  {
    Hibernate.initialize(obj);
    
    if (obj == null)
    {
      return null;
    }
    
    if (HibernateProxy.class.isInstance(obj))
    {
      HibernateProxy proxy = (HibernateProxy) obj;
      return proxy.getHibernateLazyInitializer().getImplementation();
    }
    
    return obj;
  }

NOTE: Hibernate.initialise(object) doesn't work, because although it may
flesh out the proxied object, it returns void, so we are still left
holding onto the proxy.

I don't particularly like this approach, because for every factory
method we need to remember to deproxy the found object, and where
collections are returned, we'd need to deproxy every object in the
collection.

An alternative solution is to write a
ProxyAwareObjectIdentityRetrievalStrategy implementation. This is
feasible because all (I think) classes that use an
ObjectIdentityRetrievalStrategy support dependency injection.

I've had a go at this, and got the cglib enhanced case taken care of,
but I also thought that the case where a Jdk proxy is returned needs to
be addressed also. I've found a simple way to detect a Jdk proxy, but
cannot figure how to get the class name we actually want out of it, as
Jdk proxies only work on interfaces, and it's the InvokationHandler that
does all the delegation.



Note: to try the test and fix You'll have to add
    <dependency>
      <groupId>cglib</groupId>
      <artifactId>cglib</artifactId>
      <version>2.1_3</version>
      <optional>true</optional>
    </dependency>

to the core pom.xml for these to compile and test
/* Copyright 2004, 2005, 2006, 2007 Acegi Technology Pty Limited
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */
package org.acegisecurity.acls.objectidentity;

import java.io.Serializable;
import java.lang.reflect.Method;

import org.acegisecurity.acls.IdentityUnavailableException;
import org.acegisecurity.acls.objectidentity.ObjectIdentity;
import org.acegisecurity.acls.objectidentity.ObjectIdentityImpl;
import org.acegisecurity.acls.objectidentity.ObjectIdentityRetrievalStrategy;
import org.springframework.util.Assert;

/**
 * Basic implementation of [EMAIL PROTECTED] ObjectIdentityRetrievalStrategy} that is aware
 * of proxying technologies.
 * 
 * @author Simon van der Sluis
 * @version $Id: ObjectIdentityRetrievalStrategyImpl.java,v 1.1 2007/02/23
 *          01:55:25 simonv Exp $
 */
public class ProxyAwareObjectIdentityRetrievalStrategy implements
	ObjectIdentityRetrievalStrategy {

    /**
     * As per [EMAIL PROTECTED] ObjectIdentityRetrievalStrategy#getObjectIdentity(Object)}
     * but if the object is a proxy ensures that the [EMAIL PROTECTED] ObjectIdentity}
     * generated contains the real class of the object not the class of the
     * proxy.
     * @param object The [EMAIL PROTECTED] Object} to create an [EMAIL PROTECTED] ObjectIdentity} for
     * @return The [EMAIL PROTECTED] ObjectIdentity} to represent object
     */
    public ObjectIdentity getObjectIdentity(Object object) {
	Assert.notNull(object);
	Class clazz = object.getClass();
	
	// check if it's a cglib proxy
	if (net.sf.cglib.proxy.Proxy.isProxyClass(clazz)) {
	    clazz = clazz.getSuperclass();
	}
	
	// check if it's cglib enhanced
	else if (net.sf.cglib.proxy.Enhancer.isEnhanced(clazz)) {
	    clazz = clazz.getSuperclass();
	}

	// check if it's a jdk proxy
	if (java.lang.reflect.Proxy.isProxyClass(clazz)) {
	    // TODO Figure out if / how to make this work
	    throw new IllegalArgumentException("JDK proxies not yet supported");
	}
	
	// now create the ObjectIdentity
	Object result = null;
	try {
	    Method method = clazz.getMethod("getId", new Class[] {});
	    result = method.invoke(object, new Object[] {});
	} catch (Exception e) {
	    throw new IdentityUnavailableException(
		    "Could not extract identity from object " + object, e);
	}

	Assert.isInstanceOf(Serializable.class, result,
		"Getter must provide a return value of type Serializable");
	Serializable id = (Serializable) result;

	return new ObjectIdentityImpl(clazz, id);

    }

}
/* Copyright 2004, 2005, 2006, 2007 Acegi Technology Pty Limited
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.acegisecurity.acls.objectIdentity;

import java.io.Serializable;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

import org.acegisecurity.acls.objectidentity.ObjectIdentity;
import org.acegisecurity.acls.objectidentity.ObjectIdentityRetrievalStrategy;
import org.acegisecurity.acls.objectidentity.ProxyAwareObjectIdentityRetrievalStrategy;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.NoOp;

import junit.framework.TestCase;

/**
 * 
 * This class is intended to test [EMAIL PROTECTED] ProxyAwareObjectIdentityRetrievalStrategy}
 * <br/><br/>
 * <dt><b>Code Review History:</b></dt>
 * <table border="1">
 * <tr><th>Date</th><th>Revision</th><th>Reviewer</th><th>Description</th></tr>
 * <tr>
 *  <td>15/11/2007</td>
 *  <td>1.1</td>
 *  <td>svanders</td>
 *  <td>Class Created</td>
 * </tr>
 * </table>
 *
 * @version $Revision: 1.1 $
 * @author Simon van der Sluis
 */
public class ProxyAwareObjectIdentityRetrievalStrategyTest extends TestCase
{
  
  /** The [EMAIL PROTECTED] ObjectIdentityRetrievalStrategy} to test */
  private ProxyAwareObjectIdentityRetrievalStrategy strategy;
  
  
  /**
   * Set up a test 
   * @throws Exception
   */
  protected void setUp() throws Exception
  {
    strategy = new ProxyAwareObjectIdentityRetrievalStrategy();
  }

  
  /**
   * Tear down a test
   * @throws Exception
   */
  protected void tearDown() throws Exception
  {
    
  }

  
  /**
   * Tests that a cglib enhanced class is successfully detected and the
   * undelying class name is used for the [EMAIL PROTECTED] ObjectIdentity} created
   * not the Enhanced class
   */
  public void testCgLibEnhanced()
  {
    Object proxy = Enhancer.create(Foo.class, NoOp.INSTANCE);
    assertNotNull(proxy);
    assertFalse(Foo.class == proxy.getClass());
    assertTrue(Foo.class.isInstance(proxy));
    ObjectIdentity oid = strategy.getObjectIdentity(proxy);
    
    assertEquals(Foo.class, oid.getJavaType());
    assertEquals(Foo.ID, oid.getIdentifier());
  }

  /**
   * Tests that a cglib enhanced class with an interface is successfully 
   * detected and the undelying class name is used for the 
   * [EMAIL PROTECTED] ObjectIdentity} created not the Enhanced class
   */
  public void testCgLibEnhancedWithInterface()
  {
    Object proxy = Enhancer.create(BarImpl.class, NoOp.INSTANCE);
    assertNotNull(proxy);
    assertFalse(Bar.class == proxy.getClass());
    assertTrue(BarImpl.class.isInstance(proxy));
    assertTrue(Bar.class.isInstance(proxy));
    ObjectIdentity oid = strategy.getObjectIdentity(proxy);
    
    assertEquals(BarImpl.class, oid.getJavaType());
    assertEquals(BarImpl.ID, oid.getIdentifier());
  }
  

  /**
   * Tests that a Jdk proxied class is successfully detected and the
   * undelying class name is used for the [EMAIL PROTECTED] ObjectIdentity} created
   * not the Proxy class
   */
  // TODO Figure out if / how to make this work
//  public void testJdkProxy()
//  {
//    Bar proxy 
//      = (Bar) java.lang.reflect.Proxy.newProxyInstance(Bar.class.getClassLoader(), 
//                                                 new Class[] {Bar.class}, 
//                                                 new ProxyMeInvoker());
//    
//    assertNotNull(proxy);
//    assertFalse(BarImpl.class == proxy.getClass());
//    assertTrue(Bar.class.isInstance(proxy));
//    ObjectIdentity oid = strategy.getObjectIdentity(proxy);
//    
//    assertEquals(Foo.class, oid.getJavaType());
//    assertEquals(Foo.ID, oid.getIdentifier());
//  }
  

  /**
   * A class suitable for creating an [EMAIL PROTECTED] ObjectIdentity} for  to use in 
   * tests
   */
  public static class Foo
  {
    
    public static final Long ID = new Long(1L);
    
    
    public Foo()
    {
      super();
    }

    /** 
     * getID() makes this class suitable for creating an [EMAIL PROTECTED] ObjectIdentity}
     * @return
     */
    public Serializable getId()
    {
      return ID;
    }
    
  }
  
  /**
   * An interface suitable for creating an [EMAIL PROTECTED] ObjectIdentity} for  to use in 
   * tests
   */
  static interface Bar
  {
    
      /** 
       * getID() makes this class suitable for creating an [EMAIL PROTECTED] ObjectIdentity}
       * @return
       */
    Serializable getId();
  }
  
  
  /**
   * A class suitable for creating an [EMAIL PROTECTED] ObjectIdentity} for  to use in 
   * tests
   */
  public static class BarImpl implements Bar
  {
    public static final Long ID = new Long(2L);
  
    
    public BarImpl()
    {
     
    }

    /** 
     * getID() makes this class suitable for creating an [EMAIL PROTECTED] ObjectIdentity}
     * @return
     */
    public Serializable getId()
    {
      return ID;
    }
  }

  public static class ProxyMeInvoker implements InvocationHandler
  {

    private BarImpl invokeOn = new BarImpl();
    
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
    {
      if (method.getName().equals("getClass"))
      {
        return proxy.getClass();
      }
      Object rvalue = method.invoke(invokeOn, args);
      return rvalue;
    }
    
  }
  
  
}
-------------------------------------------------------------------------
This SF.net email is sponsored by: Splunk Inc.
Still grepping through log files to find problems?  Stop.
Now Search log events and configuration files using AJAX and a browser.
Download your FREE copy of Splunk now >> http://get.splunk.com/
_______________________________________________
Home: http://acegisecurity.org
Acegisecurity-developer mailing list
Acegisecurity-developer@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/acegisecurity-developer

Reply via email to