-
Notifications
You must be signed in to change notification settings - Fork 813
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
WW-5428 Allowlist capability should resolve Hibernate proxies when disableProxyObjects is not set #967
Merged
Merged
WW-5428 Allowlist capability should resolve Hibernate proxies when disableProxyObjects is not set #967
Changes from all commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
2f81418
WW-5428 Allowlist capability should resolve Hibernate proxies when di…
kusalk abf03fd
WW-5428 Clean up SecurityMemberAccessProxyTest
kusalk c965812
WW-5428 Add unit test coverage for Hibernate proxy resolution
kusalk c6f394a
WW-5428 Add log warning for Hibernate entities
kusalk 8555dc2
WW-5428 Add log warning for allowlist disabled
kusalk 05680d7
WW-5428 Amend log warning for missing allowlist entry
kusalk File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -26,12 +26,16 @@ | |
import org.apache.commons.lang3.reflect.FieldUtils; | ||
import org.apache.struts2.ognl.ProviderAllowlist; | ||
import org.apache.struts2.ognl.ThreadAllowlist; | ||
import org.hibernate.proxy.HibernateProxy; | ||
import org.hibernate.proxy.LazyInitializer; | ||
import org.junit.Before; | ||
import org.junit.Test; | ||
|
||
import java.lang.reflect.Field; | ||
import java.lang.reflect.InvocationHandler; | ||
import java.lang.reflect.Member; | ||
import java.lang.reflect.Method; | ||
import java.lang.reflect.Proxy; | ||
import java.util.Arrays; | ||
import java.util.Collection; | ||
import java.util.HashMap; | ||
|
@@ -853,9 +857,11 @@ public void testPackageNameExclusionAsCommaDelimited() { | |
assertTrue("package java.lang. is accessible!", actual); | ||
} | ||
|
||
/** | ||
* Test that the allowlist is enforced correctly for classes. | ||
*/ | ||
@Test | ||
public void classInclusion() throws Exception { | ||
|
||
sma.useEnforceAllowlistEnabled(Boolean.TRUE.toString()); | ||
|
||
TestBean2 bean = new TestBean2(); | ||
|
@@ -868,6 +874,9 @@ public void classInclusion() throws Exception { | |
assertTrue(sma.checkAllowlist(bean, method)); | ||
} | ||
|
||
/** | ||
* Test that the allowlist is enforced correctly for packages. | ||
*/ | ||
@Test | ||
public void packageInclusion() throws Exception { | ||
sma.useEnforceAllowlistEnabled(Boolean.TRUE.toString()); | ||
|
@@ -882,6 +891,9 @@ public void packageInclusion() throws Exception { | |
assertTrue(sma.checkAllowlist(bean, method)); | ||
} | ||
|
||
/** | ||
* Test that the allowlist doesn't allow inherited methods unless the declaring class is also allowlisted. | ||
*/ | ||
@Test | ||
public void classInclusion_subclass() throws Exception { | ||
sma.useEnforceAllowlistEnabled(Boolean.TRUE.toString()); | ||
|
@@ -893,6 +905,9 @@ public void classInclusion_subclass() throws Exception { | |
assertFalse(sma.checkAllowlist(bean, method)); | ||
} | ||
|
||
/** | ||
* Test that the allowlist allows inherited methods when both the target and declaring class are allowlisted. | ||
*/ | ||
@Test | ||
public void classInclusion_subclass_both() throws Exception { | ||
sma.useEnforceAllowlistEnabled(Boolean.TRUE.toString()); | ||
|
@@ -904,6 +919,10 @@ public void classInclusion_subclass_both() throws Exception { | |
assertTrue(sma.checkAllowlist(bean, method)); | ||
} | ||
|
||
/** | ||
* Test that the allowlist doesn't allow inherited methods unless the package of the declaring class is also | ||
* allowlisted. | ||
*/ | ||
@Test | ||
public void packageInclusion_subclass() throws Exception { | ||
sma.useEnforceAllowlistEnabled(Boolean.TRUE.toString()); | ||
|
@@ -915,6 +934,37 @@ public void packageInclusion_subclass() throws Exception { | |
assertFalse(sma.checkAllowlist(bean, method)); | ||
} | ||
|
||
/** | ||
* When the allowlist is enabled and proxy object access is disallowed, Hibernate proxies should not be allowed. | ||
*/ | ||
@Test | ||
public void classInclusion_hibernateProxy_disallowProxyObjectAccess() throws Exception { | ||
FooBarInterface proxyObject = mockHibernateProxy(new FooBar(), FooBarInterface.class); | ||
Method proxyMethod = proxyObject.getClass().getMethod("fooLogic"); | ||
|
||
sma.useEnforceAllowlistEnabled(Boolean.TRUE.toString()); | ||
sma.useDisallowProxyObjectAccess(Boolean.TRUE.toString()); | ||
sma.useAllowlistClasses(FooBar.class.getName()); | ||
|
||
assertFalse(sma.checkAllowlist(proxyObject, proxyMethod)); | ||
} | ||
|
||
/** | ||
* When the allowlist is enabled and proxy object access is allowed, Hibernate proxies should be allowlisted based | ||
* on their underlying target object. Class allowlisting should work as expected. | ||
*/ | ||
@Test | ||
public void classInclusion_hibernateProxy_allowProxyObjectAccess() throws Exception { | ||
FooBarInterface proxyObject = mockHibernateProxy(new FooBar(), FooBarInterface.class); | ||
Method proxyMethod = proxyObject.getClass().getMethod("fooLogic"); | ||
|
||
sma.useEnforceAllowlistEnabled(Boolean.TRUE.toString()); | ||
sma.useDisallowProxyObjectAccess(Boolean.FALSE.toString()); | ||
sma.useAllowlistClasses(FooBar.class.getName()); | ||
|
||
assertTrue(sma.checkAllowlist(proxyObject, proxyMethod)); | ||
} | ||
|
||
@Test | ||
public void packageInclusion_subclass_both() throws Exception { | ||
sma.useEnforceAllowlistEnabled(Boolean.TRUE.toString()); | ||
|
@@ -931,6 +981,15 @@ public void packageInclusion_subclass_both() throws Exception { | |
private static String formGetterName(String propertyName) { | ||
return "get" + propertyName.substring(0, 1).toUpperCase() + propertyName.substring(1); | ||
} | ||
|
||
@SuppressWarnings("unchecked") | ||
private static <T> T mockHibernateProxy(T originalObject, Class<T> proxyInterface) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Mocking a Hibernate proxy isn't perfect - integration/acceptance tests would offer better protection against regressions but I'd prefer not to complicate the tests further by introducing a Hibernate session factory etc |
||
return (T) Proxy.newProxyInstance( | ||
proxyInterface.getClassLoader(), | ||
new Class<?>[]{proxyInterface, HibernateProxy.class}, | ||
new DummyHibernateProxyHandler(originalObject) | ||
); | ||
} | ||
} | ||
|
||
class FooBar implements FooBarInterface { | ||
|
@@ -1042,10 +1101,28 @@ public static String sayHello() { | |
} | ||
|
||
protected static Field getFieldByName(String fieldName) throws NoSuchFieldException { | ||
if (fieldName != null && fieldName.length() > 0) { | ||
if (fieldName != null && !fieldName.isEmpty()) { | ||
return StaticTester.class.getDeclaredField(fieldName); | ||
} else { | ||
throw new NoSuchFieldException("field: " + fieldName + " does not exist"); | ||
} | ||
} | ||
} | ||
|
||
class DummyHibernateProxyHandler implements InvocationHandler { | ||
private final Object instance; | ||
|
||
public DummyHibernateProxyHandler(Object instance) { | ||
this.instance = instance; | ||
} | ||
|
||
@Override | ||
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { | ||
if (HibernateProxy.class.getMethod("getHibernateLazyInitializer").equals(method)) { | ||
LazyInitializer initializer = mock(LazyInitializer.class); | ||
when(initializer.getImplementation()).thenReturn(instance); | ||
return initializer; | ||
} | ||
return method.invoke(instance, args); | ||
} | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wouldn't be good to log this info? Maybe even in WARN level if
struts.devMode
is enabled, wdyt?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm yeah doesn't hurt to add some logging - will do
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done