[
https://issues.apache.org/jira/browse/GROOVY-11813?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=18048336#comment-18048336
]
Saravanan Subiramaniam commented on GROOVY-11813:
-------------------------------------------------
Debugged and found that using a custom MetaClassImpl is the difference. In our
project, we want to implement security to allow/dis-allow access to
methods/constructors/static method calls. For example, we want to throw error
if java.sql or java.util.concurrent APIs are used in Groovy script. So, we have
implemented custom SecureMetaClass (which extends MetaClass) and overridden
methods like {{{}invokeConstructor{}}}, {{invokeStaticMethod}} and
{{invokeMethod}} to intercept the calls and perform security checks. When I
remove this SecureMetaClass, then minus operation worked fine in our product.
For now, we'll use normal MetaClass for Date - to fix this issue. But I'm
wondering what's wrong with our implementation and is there a recommended/best
practices for doing such checks. I'm aware of SecureASTCustomizer, but it's a
compile-time check, which may not cover all the scenarios and hence we wanted
to do check at runtime.
For reference, this is how we use SecureMetaClass.
{code}
MetaClassRegistry metaClassRegistry = getMetaClassRegistry();
metaClassRegistry.setMetaClassCreationHandle(new
SecureMetaClassCreationHandle());
{code}
the SecureMetaClassCreationHandle looks like this
{code}
public class SecureMetaClassCreationHandle extends
MetaClassRegistry.MetaClassCreationHandle {
@Override
protected MetaClass createNormalMetaClass(Class theClass, MetaClassRegistry
registry) {
if (GeneratedClosure.class.isAssignableFrom(theClass)) {
return new ClosureMetaClass(registry, theClass);
} else {
return new SecureMetaClass(registry, theClass);
}
}
}
{code}
the SecureMetaClass looks like this
{code}
public class SecureMetaClass extends MetaClassImpl {
private static final String staticOrNullTarget = "[Static|Null]";
public SecureMetaClass(Class theClass, MetaMethod[] add) {
super(theClass, add);
}
public SecureMetaClass(Class theClass) {
super(theClass);
}
public SecureMetaClass(MetaClassRegistry registry, Class theClass,
MetaMethod[] add) {
super(registry, theClass, add);
}
public SecureMetaClass(MetaClassRegistry registry, Class theClass) {
super(registry, theClass);
}
@Override
public CallSite createPojoCallSite(CallSite site, Object receiver, Object[]
args) {
CallSiteKey callSiteKey = new CallSiteKey(getReceiverName(receiver),
site.getName());
checkRestrictions(receiver, callSiteKey, args);
return super.createPojoCallSite(site, receiver, args);
}
@Override
public CallSite createStaticSite(CallSite site, Object[] args) {
CallSiteKey callSiteKey = new CallSiteKey(staticOrNullTarget,
site.getName());
checkRestrictions(null, callSiteKey, args);
return super.createStaticSite(site, args);
}
@Override
public Object invokeConstructor(Object[] arguments) {
MetaReceiver metaReceiver = new MetaReceiver(null, getTheClass());
CallSiteKey callSiteKey = new CallSiteKey(staticOrNullTarget,
"<$constructor$>");
checkRestrictions(metaReceiver, callSiteKey, arguments);
return super.invokeConstructor(arguments);
}
@Override
public Object invokeStaticMethod(Object object, String methodName, Object[]
arguments) {
CallSiteKey callSiteKey = new CallSiteKey(staticOrNullTarget,
methodName);
checkRestrictions(null, callSiteKey, arguments);
return super.invokeStaticMethod(object, methodName, arguments);
}
@Override
public Object invokeMethod(Object object, String methodName, Object
arguments) {
CallSiteKey callSiteKey = new CallSiteKey(getReceiverName(object),
methodName);
if (arguments == null) {
checkRestrictions(object, callSiteKey, MetaClassHelper.EMPTY_ARRAY);
}
if (arguments instanceof Tuple<?> tuple) {
checkRestrictions(object, callSiteKey, tuple.toArray());
}
if (arguments instanceof Object[] object1s) {
checkRestrictions(object, callSiteKey, object1s);
}
return super.invokeMethod(object, methodName, arguments);
}
private String getReceiverName(Object receiver) {
if (receiver != null) {
if (receiver instanceof GroovyObject) {
return
GroovyObject.class.cast(receiver).getMetaClass().getTheClass().getCanonicalName();
} else {
return receiver.getClass().getCanonicalName();
}
} else {
return staticOrNullTarget;
}
}
private void checkRestrictions(Object object, CallSiteKey callSiteKey,
Object[] args) {
// Check if constructor/method/static method calls are allowed based on
whitelist/blacklist rules
}
private static class CallSiteKey {
private String siteReceiver;
private String siteName;
public CallSiteKey(String siteReceiver, String siteName) {
this.siteReceiver = siteReceiver;
this.siteName = siteName;
}
@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
CallSiteKey that = (CallSiteKey) o;
return Objects.equals(siteName, that.siteName) &&
Objects.equals(siteReceiver, that.siteReceiver);
}
@Override
public int hashCode() {
return Objects.hash(siteReceiver, siteName);
}
@Override
public String toString() {
return "CallSiteKey{" +
"sitReceiver='" + siteReceiver + "'\''" +
", siteName='" + siteName + '\'' +
'}';
}
}
}
{code}
> groovy.lang.MissingMethodException: No signature of method: minus for class:
> java.util.Date is applicable for argument types:
> (groovy.time.DatumDependentDuration)
> ------------------------------------------------------------------------------------------------------------------------------------------------------------------
>
> Key: GROOVY-11813
> URL: https://issues.apache.org/jira/browse/GROOVY-11813
> Project: Groovy
> Issue Type: Question
> Affects Versions: 5.0.2
> Reporter: Saravanan Subiramaniam
> Priority: Major
>
> Could you please help me with TimeCategory? The following script worked fine
> in Groovy 4.0.x, but fails after we upgraded to 5.0.2.
> {code}
> use(groovy.time.TimeCategory) {
> new Date() - 7.months
> }
> {code}
> Please note that this script works when running using standalone Groovy (eg:
> using groovy command), but fails in our application (Java based) with this
> error. Both groovy-dateutil and groovy-datetime dependencies are in the
> classpath. Our application uses Java 21.
> {code}
> Dynamic logic TESTUNIT_2:
> 1: *ERROR* --> use(groovy.time.TimeCategory) {
> 2: new Date() - 7.months
> 3: }
> groovy.lang.MissingMethodException: No signature of method: minus for class:
> java.util.Date is applicable for argument types:
> (groovy.time.DatumDependentDuration) values: [7 months]
> Possible solutions: minus(java.util.Date), minus(int), find(),
> find(groovy.lang.Closure), is(java.lang.Object), plus(int)
> at
> org.codehaus.groovy.runtime.ScriptBytecodeAdapter.unwrap(ScriptBytecodeAdapter.java:74)
> at
> org.codehaus.groovy.vmplugin.v8.IndyGuardsFiltersAndSignatures.unwrap(IndyGuardsFiltersAndSignatures.java:163)
> at
> org.codehaus.groovy.vmplugin.v8.IndyInterface.fromCache(IndyInterface.java:344)
> at
> custom.dynamiclogic.testunit_2.TESTUNIT_2$_run_closure1.doCall(custom.dynamiclogic.testunit_2.TESTUNIT_2.groovy:2)
> at
> custom.dynamiclogic.testunit_2.TESTUNIT_2$_run_closure1.doCall(custom.dynamiclogic.testunit_2.TESTUNIT_2.groovy)
> at
> java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
> at java.base/java.lang.reflect.Method.invoke(Method.java:580)
> at
> org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:338)
> at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:274)
> at
> org.codehaus.groovy.runtime.metaclass.ClosureMetaClass.invokeMethod(ClosureMetaClass.java:270)
> at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1008)
> at groovy.lang.Closure.call(Closure.java:471)
> at groovy.lang.Closure.call(Closure.java:450)
> at
> org.codehaus.groovy.runtime.GroovyCategorySupport$ThreadCategoryInfo.use(GroovyCategorySupport.java:124)
> at
> org.codehaus.groovy.runtime.GroovyCategorySupport.use(GroovyCategorySupport.java:262)
> at
> org.codehaus.groovy.runtime.DefaultGroovyMethods.use(DefaultGroovyMethods.java:17206)
> at org.codehaus.groovy.runtime.dgm$1263.doMethodInvoke(Unknown Source)
> at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1235)
> at
> org.codehaus.groovy.vmplugin.v8.IndyInterface.fromCache(IndyInterface.java:344)
> at
> custom.dynamiclogic.testunit_2.TESTUNIT_2.run(custom.dynamiclogic.testunit_2.TESTUNIT_2.groovy:1)
> {code}
--
This message was sent by Atlassian Jira
(v8.20.10#820010)