Merge pull request #18137 from owen-mc/java/jax-rs-annotation-inheritance

Java: Update JAX-RS annotation inheritance
This commit is contained in:
Owen Mansel-Chan
2025-01-21 15:26:47 +00:00
committed by GitHub
10 changed files with 663 additions and 12 deletions

View File

@@ -0,0 +1,4 @@
---
category: minorAnalysis
---
* We now allow classes which don't have any JAX-RS annotations to inherit JAX-RS annotations from superclasses or interfaces. This is not allowed in the JAX-RS specification, but some implementations, like Apache CXF, allow it. This may lead to more alerts being found.

View File

@@ -147,6 +147,21 @@ private predicate hasPathAnnotation(Annotatable annotatable) {
)
}
/**
* Holds if the class has or inherits the JaxRs `@Path` annotation.
*/
private predicate hasOrInheritsPathAnnotation(Class c) {
hasPathAnnotation(c)
or
// Note that by the JAX-RS spec, JAX-RS annotations on classes and interfaces
// are not inherited, but some implementations, like Apache CXF, do inherit
// them. I think this only applies if there are no JaxRS annotations on the
// class itself, as that is the rule in the JAX-RS spec for method
// annotations.
hasPathAnnotation(c.getAnAncestor()) and
not exists(c.getAnAnnotation().(JaxRSAnnotation))
}
/**
* A method which is annotated with one or more JaxRS resource type annotations e.g. `@GET`, `@POST` etc.
*/
@@ -191,7 +206,7 @@ class JaxRsResourceMethod extends Method {
class JaxRsResourceClass extends Class {
JaxRsResourceClass() {
// A root resource class has a @Path annotation on the class.
hasPathAnnotation(this)
hasOrInheritsPathAnnotation(this)
or
// A sub-resource
exists(JaxRsResourceClass resourceClass, Method method |
@@ -227,7 +242,7 @@ class JaxRsResourceClass extends Class {
/**
* Holds if this class is a "root resource" class
*/
predicate isRootResource() { hasPathAnnotation(this) }
predicate isRootResource() { hasOrInheritsPathAnnotation(this) }
/**
* Gets a `Constructor` that may be called by a JaxRS container to construct this class reflectively.

View File

@@ -156,12 +156,12 @@ class NotAResourceClass1Jakarta {
class NotAResourceClass2Jakarta {
}
class ExtendsJakartaRs1 extends JakartaRs1 {
class ExtendsJakartaRs1 extends JakartaRs1 { // $ RootResourceClass
@Override
int Get() { // $ ResourceMethod
return 1;
}
@Override
@QueryParam("") // $ InjectionAnnotation
void Post() {
@@ -189,12 +189,12 @@ class ExtendsJakartaRs1 extends JakartaRs1 {
}
@Produces(MediaType.TEXT_XML) // $ ProducesAnnotation=text/xml
class ExtendsJakartaRs1WithProducesAnnotation extends JakartaRs1 {
class ExtendsJakartaRs1WithProducesAnnotation extends JakartaRs1 { // Not a root resource class because it has a JAX-RS annotation
@Override
int Get() { // $ ResourceMethod=text/xml
return 2;
}
@Override
@QueryParam("") // $ InjectionAnnotation
void Post() {
@@ -212,4 +212,4 @@ class ExtendsJakartaRs1WithProducesAnnotation extends JakartaRs1 {
@Override
void Options() { // $ ResourceMethod=text/xml
}
}
}

View File

@@ -0,0 +1,162 @@
import java.io.InputStream;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.DELETE;
import jakarta.ws.rs.PUT;
import jakarta.ws.rs.OPTIONS;
import jakarta.ws.rs.HEAD;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.BeanParam;
import jakarta.ws.rs.CookieParam;
import jakarta.ws.rs.FormParam;
import jakarta.ws.rs.HeaderParam;
import jakarta.ws.rs.MatrixParam;
import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.QueryParam;
import jakarta.ws.rs.client.Client;
import jakarta.ws.rs.core.Context;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.MultivaluedMap;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.ext.MessageBodyReader;
class ExtendsJakartaRs3 extends JakartaRs3 { // $ RootResourceClass
@Override
public int Get() { // $ ResourceMethod
return 1;
}
@Override
public @QueryParam("") // $ InjectionAnnotation
void Post() {
}
@Override
public double Delete() { // $ ResourceMethod=application/json
return 1.0;
}
@Override
public void Put() { // $ ResourceMethod=text/html
}
@Produces("application/json") // $ ProducesAnnotation=application/json
@Override
public void Options() { // not a resource method because it has a jax-rs annotation, so it doesn't inherit any jax-rs annotations
}
@Produces(MediaType.TEXT_XML) // $ ProducesAnnotation=text/xml
@Override
public void Head() { // not a resource method because it has a jax-rs annotation, so it doesn't inherit any jax-rs annotations
}
}
@Produces(MediaType.TEXT_XML) // $ ProducesAnnotation=text/xml
class ExtendsJakartaRs3WithProducesAnnotation extends JakartaRs3 { // Not a root resource class because it has a JAX-RS annotation
@Override
public int Get() { // $ ResourceMethod=text/xml
return 2;
}
@Override
public @QueryParam("") // $ InjectionAnnotation
void Post() {
}
@Override
public double Delete() { // $ ResourceMethod=application/json
return 2.0;
}
@Override
public void Put() { // $ ResourceMethod=text/html
}
@Override
public void Options() { // $ ResourceMethod=text/xml
}
}
@Path("")
public class JakartaRs3 implements JakartaRsInterface { // $ RootResourceClass
public JakartaRs3() { // $ InjectableConstructor
}
@Override
public int Get() { // $ ResourceMethod ResourceMethodOnResourceClass
return 1; // $ XssSink
}
@Override
public void Post() { // $ ResourceMethod ResourceMethodOnResourceClass
}
@Produces("application/json") // $ ProducesAnnotation=application/json
@Override
public double Delete() { // not a resource method because it has a jax-rs annotation, so it doesn't inherit any jax-rs annotations
return 1.0;
}
@Produces(MediaType.TEXT_HTML) // $ ProducesAnnotation=text/html
@Override
public void Put() { // not a resource method because it has a jax-rs annotation, so it doesn't inherit any jax-rs annotations
}
@Override
public void Options() { // $ ResourceMethod ResourceMethodOnResourceClass
}
@Override
public void Head() { // $ ResourceMethod ResourceMethodOnResourceClass
}
@Path("")
NonRootResourceClassJakarta subResourceLocator() { // $ SubResourceLocator
return null;
}
public class NonRootResourceClassJakarta { // $ NonRootResourceClass
@GET
int Get() { // $ ResourceMethod ResourceMethodOnResourceClass
return 0; // $ XssSink
}
@Produces("text/html") // $ ProducesAnnotation=text/html
@POST
boolean Post() { // $ ResourceMethod=text/html ResourceMethodOnResourceClass
return false; // $ XssSink
}
@Produces(MediaType.TEXT_PLAIN) // $ ProducesAnnotation=text/plain
@DELETE
double Delete() { // $ ResourceMethod=text/plain ResourceMethodOnResourceClass
return 0.0;
}
@Path("")
AnotherNonRootResourceClassJakarta subResourceLocator1() { // $ SubResourceLocator
return null;
}
@GET
@Path("")
NotAResourceClass1Jakarta NotASubResourceLocator1() { // $ ResourceMethod ResourceMethodOnResourceClass
return null; // $ XssSink
}
@GET
NotAResourceClass2Jakarta NotASubResourceLocator2() { // $ ResourceMethod ResourceMethodOnResourceClass
return null; // $ XssSink
}
NotAResourceClass2Jakarta NotASubResourceLocator3() {
return null;
}
}
}

View File

@@ -0,0 +1,108 @@
import java.io.InputStream;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.DELETE;
import jakarta.ws.rs.PUT;
import jakarta.ws.rs.OPTIONS;
import jakarta.ws.rs.HEAD;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.BeanParam;
import jakarta.ws.rs.CookieParam;
import jakarta.ws.rs.FormParam;
import jakarta.ws.rs.HeaderParam;
import jakarta.ws.rs.MatrixParam;
import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.QueryParam;
import jakarta.ws.rs.client.Client;
import jakarta.ws.rs.core.Context;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.MultivaluedMap;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.ext.MessageBodyReader;
// By the JAX-RS spec, this is not a resource class because it doesn't
// have a @Path annotation. Inheritance of class or interface annotations
// is not supported in JAX-RS. However, this is a resource class for some
// implementations, like Apache CXF, that allow inheritance of JAX-RS
// annotations on classes and interfaces.
public class JakartaRs4 implements JakartaRsInterface { // $ RootResourceClass
public JakartaRs4() { // $ InjectableConstructor
}
@Override
public int Get() { // $ ResourceMethod ResourceMethodOnResourceClass
return 1; // $ XssSink
}
@Override
public void Post() { // $ ResourceMethod ResourceMethodOnResourceClass
}
@Produces("application/json") // $ ProducesAnnotation=application/json
@Override
public double Delete() { // not a resource method because it has a jax-rs annotation, so it doesn't inherit any jax-rs annotations
return 1.0;
}
@Produces(MediaType.TEXT_HTML) // $ ProducesAnnotation=text/html
@Override
public void Put() { // not a resource method because it has a jax-rs annotation, so it doesn't inherit any jax-rs annotations
}
@Override
public void Options() { // $ ResourceMethod ResourceMethodOnResourceClass
}
@Override
public void Head() { // $ ResourceMethod ResourceMethod ResourceMethodOnResourceClass
}
@Path("")
NonRootResourceClassJakarta subResourceLocator() {
return null;
}
public class NonRootResourceClassJakarta { // $ NonRootResourceClass
@GET
int Get() { // $ ResourceMethod ResourceMethodOnResourceClass
return 0; // $ XssSink
}
@Produces("text/html") // $ ProducesAnnotation=text/html
@POST
boolean Post() { // $ ResourceMethod=text/html ResourceMethodOnResourceClass
return false; // $ XssSink
}
@Produces(MediaType.TEXT_PLAIN) // $ ProducesAnnotation=text/plain
@DELETE
double Delete() { // $ ResourceMethod=text/plain ResourceMethodOnResourceClass
return 0.0;
}
@Path("")
AnotherNonRootResourceClassJakarta subResourceLocator1() { // $ SubResourceLocator
return null;
}
@GET
@Path("")
NotAResourceClass1Jakarta NotASubResourceLocator1() { // $ ResourceMethod ResourceMethodOnResourceClass
return null; // $ XssSink
}
@GET
NotAResourceClass2Jakarta NotASubResourceLocator2() { // $ ResourceMethod ResourceMethodOnResourceClass
return null; // $ XssSink
}
NotAResourceClass2Jakarta NotASubResourceLocator3() {
return null;
}
}
}

View File

@@ -0,0 +1,46 @@
import java.io.InputStream;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.DELETE;
import jakarta.ws.rs.PUT;
import jakarta.ws.rs.OPTIONS;
import jakarta.ws.rs.HEAD;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.BeanParam;
import jakarta.ws.rs.CookieParam;
import jakarta.ws.rs.FormParam;
import jakarta.ws.rs.HeaderParam;
import jakarta.ws.rs.MatrixParam;
import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.QueryParam;
import jakarta.ws.rs.client.Client;
import jakarta.ws.rs.core.Context;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.MultivaluedMap;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.ext.MessageBodyReader;
@Path("/resource")
public interface JakartaRsInterface {
@GET
int Get(); // $ ResourceMethod
@POST
void Post(); // $ ResourceMethod
@DELETE
double Delete(); // $ ResourceMethod
@PUT
void Put(); // $ ResourceMethod
@OPTIONS
void Options(); // $ ResourceMethod
@HEAD
void Head(); // $ ResourceMethod
}

View File

@@ -156,12 +156,12 @@ class NotAResourceClass1 {
class NotAResourceClass2 {
}
class ExtendsJaxRs1 extends JaxRs1 {
class ExtendsJaxRs1 extends JaxRs1 { // $ RootResourceClass
@Override
int Get() { // $ ResourceMethod
return 1;
}
@Override
@QueryParam("") // $ InjectionAnnotation
void Post() {
@@ -189,12 +189,12 @@ class ExtendsJaxRs1 extends JaxRs1 {
}
@Produces(MediaType.TEXT_XML) // $ ProducesAnnotation=text/xml
class ExtendsJaxRs1WithProducesAnnotation extends JaxRs1 {
class ExtendsJaxRs1WithProducesAnnotation extends JaxRs1 { // Not a root resource class because it has a JAX-RS annotation
@Override
int Get() { // $ ResourceMethod=text/xml
return 2;
}
@Override
@QueryParam("") // $ InjectionAnnotation
void Post() {
@@ -212,4 +212,4 @@ class ExtendsJaxRs1WithProducesAnnotation extends JaxRs1 {
@Override
void Options() { // $ ResourceMethod=text/xml
}
}
}

View File

@@ -0,0 +1,162 @@
import java.io.InputStream;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.DELETE;
import javax.ws.rs.PUT;
import javax.ws.rs.OPTIONS;
import javax.ws.rs.HEAD;
import javax.ws.rs.Path;
import javax.ws.rs.BeanParam;
import javax.ws.rs.CookieParam;
import javax.ws.rs.FormParam;
import javax.ws.rs.HeaderParam;
import javax.ws.rs.MatrixParam;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.client.Client;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response;
import javax.ws.rs.ext.MessageBodyReader;
class ExtendsJaxRs3 extends JaxRs3 { // $ RootResourceClass
@Override
public int Get() { // $ ResourceMethod
return 1;
}
@Override
public @QueryParam("") // $ InjectionAnnotation
void Post() {
}
@Override
public double Delete() { // $ ResourceMethod=application/json
return 1.0;
}
@Override
public void Put() { // $ ResourceMethod=text/html
}
@Produces("application/json") // $ ProducesAnnotation=application/json
@Override
public void Options() { // not a resource method because it has a jax-rs annotation, so it doesn't inherit any jax-rs annotations
}
@Produces(MediaType.TEXT_XML) // $ ProducesAnnotation=text/xml
@Override
public void Head() { // not a resource method because it has a jax-rs annotation, so it doesn't inherit any jax-rs annotations
}
}
@Produces(MediaType.TEXT_XML) // $ ProducesAnnotation=text/xml
class ExtendsJaxRs3WithProducesAnnotation extends JaxRs3 { // Not a root resource class because it has a JAX-RS annotation
@Override
public int Get() { // $ ResourceMethod=text/xml
return 2;
}
@Override
public @QueryParam("") // $ InjectionAnnotation
void Post() {
}
@Override
public double Delete() { // $ ResourceMethod=application/json
return 2.0;
}
@Override
public void Put() { // $ ResourceMethod=text/html
}
@Override
public void Options() { // $ ResourceMethod=text/xml
}
}
@Path("")
public class JaxRs3 implements JaxRsInterface { // $ RootResourceClass
public JaxRs3() { // $ InjectableConstructor
}
@Override
public int Get() { // $ ResourceMethod ResourceMethodOnResourceClass
return 1; // $ XssSink
}
@Override
public void Post() { // $ ResourceMethod ResourceMethodOnResourceClass
}
@Produces("application/json") // $ ProducesAnnotation=application/json
@Override
public double Delete() { // not a resource method because it has a jax-rs annotation, so it doesn't inherit any jax-rs annotations
return 1.0;
}
@Produces(MediaType.TEXT_HTML) // $ ProducesAnnotation=text/html
@Override
public void Put() { // not a resource method because it has a jax-rs annotation, so it doesn't inherit any jax-rs annotations
}
@Override
public void Options() { // $ ResourceMethod ResourceMethodOnResourceClass
}
@Override
public void Head() { // $ ResourceMethod ResourceMethodOnResourceClass
}
@Path("")
NonRootResourceClass subResourceLocator() { // $ SubResourceLocator
return null;
}
public class NonRootResourceClass { // $ NonRootResourceClass
@GET
int Get() { // $ ResourceMethod ResourceMethodOnResourceClass
return 0; // $ XssSink
}
@Produces("text/html") // $ ProducesAnnotation=text/html
@POST
boolean Post() { // $ ResourceMethod=text/html ResourceMethodOnResourceClass
return false; // $ XssSink
}
@Produces(MediaType.TEXT_PLAIN) // $ ProducesAnnotation=text/plain
@DELETE
double Delete() { // $ ResourceMethod=text/plain ResourceMethodOnResourceClass
return 0.0;
}
@Path("")
AnotherNonRootResourceClass subResourceLocator1() { // $ SubResourceLocator
return null;
}
@GET
@Path("")
NotAResourceClass1 NotASubResourceLocator1() { // $ ResourceMethod ResourceMethodOnResourceClass
return null; // $ XssSink
}
@GET
NotAResourceClass2 NotASubResourceLocator2() { // $ ResourceMethod ResourceMethodOnResourceClass
return null; // $ XssSink
}
NotAResourceClass2 NotASubResourceLocator3() {
return null;
}
}
}

View File

@@ -0,0 +1,108 @@
import java.io.InputStream;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.DELETE;
import javax.ws.rs.PUT;
import javax.ws.rs.OPTIONS;
import javax.ws.rs.HEAD;
import javax.ws.rs.Path;
import javax.ws.rs.BeanParam;
import javax.ws.rs.CookieParam;
import javax.ws.rs.FormParam;
import javax.ws.rs.HeaderParam;
import javax.ws.rs.MatrixParam;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.client.Client;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response;
import javax.ws.rs.ext.MessageBodyReader;
// By the JAX-RS spec, this is not a resource class because it doesn't
// have a @Path annotation. Inheritance of class or interface annotations
// is not supported in JAX-RS. However, this is a resource class for some
// implementations, like Apache CXF, that allow inheritance of JAX-RS
// annotations on classes and interfaces.
public class JaxRs4 implements JaxRsInterface { // $ RootResourceClass
public JaxRs4() { // $ InjectableConstructor
}
@Override
public int Get() { // $ ResourceMethod ResourceMethodOnResourceClass
return 1; // $ XssSink
}
@Override
public void Post() { // $ ResourceMethod ResourceMethodOnResourceClass
}
@Produces("application/json") // $ ProducesAnnotation=application/json
@Override
public double Delete() { // not a resource method because it has a jax-rs annotation, so it doesn't inherit any jax-rs annotations
return 1.0;
}
@Produces(MediaType.TEXT_HTML) // $ ProducesAnnotation=text/html
@Override
public void Put() { // not a resource method because it has a jax-rs annotation, so it doesn't inherit any jax-rs annotations
}
@Override
public void Options() { // $ ResourceMethod ResourceMethodOnResourceClass
}
@Override
public void Head() { // $ ResourceMethod ResourceMethodOnResourceClass
}
@Path("")
NonRootResourceClass subResourceLocator() {
return null;
}
public class NonRootResourceClass { // $ NonRootResourceClass
@GET
int Get() { // $ ResourceMethod ResourceMethodOnResourceClass
return 0; // $ XssSink
}
@Produces("text/html") // $ ProducesAnnotation=text/html
@POST
boolean Post() { // $ ResourceMethod=text/html ResourceMethodOnResourceClass
return false; // $ XssSink
}
@Produces(MediaType.TEXT_PLAIN) // $ ProducesAnnotation=text/plain
@DELETE
double Delete() { // $ ResourceMethod=text/plain ResourceMethodOnResourceClass
return 0.0;
}
@Path("")
AnotherNonRootResourceClass subResourceLocator1() { // $ SubResourceLocator
return null;
}
@GET
@Path("")
NotAResourceClass1 NotASubResourceLocator1() { // $ ResourceMethod ResourceMethodOnResourceClass
return null; // $ XssSink
}
@GET
NotAResourceClass2 NotASubResourceLocator2() { // $ ResourceMethod ResourceMethodOnResourceClass
return null; // $ XssSink
}
NotAResourceClass2 NotASubResourceLocator3() {
return null;
}
}
}

View File

@@ -0,0 +1,46 @@
import java.io.InputStream;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.DELETE;
import javax.ws.rs.PUT;
import javax.ws.rs.OPTIONS;
import javax.ws.rs.HEAD;
import javax.ws.rs.Path;
import javax.ws.rs.BeanParam;
import javax.ws.rs.CookieParam;
import javax.ws.rs.FormParam;
import javax.ws.rs.HeaderParam;
import javax.ws.rs.MatrixParam;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.client.Client;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response;
import javax.ws.rs.ext.MessageBodyReader;
@Path("/resource")
public interface JaxRsInterface {
@GET
int Get(); // $ ResourceMethod
@POST
void Post(); // $ ResourceMethod
@DELETE
double Delete(); // $ ResourceMethod
@PUT
void Put(); // $ ResourceMethod
@OPTIONS
void Options(); // $ ResourceMethod
@HEAD
void Head(); // $ ResourceMethod
}