diff --git a/python/ql/lib/semmle/python/frameworks/Django.qll b/python/ql/lib/semmle/python/frameworks/Django.qll index 5f795bb0f40..ef5788b4b84 100644 --- a/python/ql/lib/semmle/python/frameworks/Django.qll +++ b/python/ql/lib/semmle/python/frameworks/Django.qll @@ -16,6 +16,7 @@ private import semmle.python.frameworks.internal.PoorMansFunctionResolution private import semmle.python.frameworks.internal.SelfRefMixin private import semmle.python.frameworks.internal.InstanceTaintStepsHelper private import semmle.python.security.dataflow.UrlRedirectCustomizations +private import semmle.python.frameworks.data.ModelsAsData /** * INTERNAL: Do not use. @@ -185,6 +186,10 @@ module Django { } } + private class MaDSubclass extends ModeledSubclass { + MaDSubclass() { this = ModelOutput::getATypeNode("django.forms.BaseForm~Subclass") } + } + /** Gets a reference to the `django.forms.forms.BaseForm` class or any subclass. */ API::Node subclassRef() { result = any(ModeledSubclass subclass).getASubclass*() } } diff --git a/python/ql/lib/semmle/python/frameworks/FastApi.qll b/python/ql/lib/semmle/python/frameworks/FastApi.qll index 0c4599d89ad..b39dd9ffd85 100644 --- a/python/ql/lib/semmle/python/frameworks/FastApi.qll +++ b/python/ql/lib/semmle/python/frameworks/FastApi.qll @@ -11,18 +11,21 @@ private import semmle.python.Concepts private import semmle.python.ApiGraphs private import semmle.python.frameworks.Pydantic private import semmle.python.frameworks.Starlette +private import semmle.python.frameworks.data.ModelsAsData /** * Provides models for the `fastapi` PyPI package. * See https://fastapi.tiangolo.com/. */ -private module FastApi { +module FastApi { /** * Provides models for FastAPI applications (an instance of `fastapi.FastAPI`). */ module App { + API::Node cls() { result = API::moduleImport("fastapi").getMember("FastAPI") } + /** Gets a reference to a FastAPI application (an instance of `fastapi.FastAPI`). */ - API::Node instance() { result = API::moduleImport("fastapi").getMember("FastAPI").getReturn() } + API::Node instance() { result = cls().getReturn() } } /** @@ -31,10 +34,14 @@ private module FastApi { * See https://fastapi.tiangolo.com/tutorial/bigger-applications/. */ module ApiRouter { - /** Gets a reference to an instance of `fastapi.ApiRouter`. */ - API::Node instance() { - result = API::moduleImport("fastapi").getMember("APIRouter").getASubclass*().getReturn() + API::Node cls() { + result = API::moduleImport("fastapi").getMember("APIRouter").getASubclass*() + or + result = ModelOutput::getATypeNode("fastapi.APIRouter~Subclass").getASubclass*() } + + /** Gets a reference to an instance of `fastapi.ApiRouter`. */ + API::Node instance() { result = cls().getReturn() } } // --------------------------------------------------------------------------- diff --git a/python/ql/lib/semmle/python/frameworks/data/internal/empty.model.yml b/python/ql/lib/semmle/python/frameworks/data/internal/empty.model.yml index 558f329ace8..4219c8ac175 100644 --- a/python/ql/lib/semmle/python/frameworks/data/internal/empty.model.yml +++ b/python/ql/lib/semmle/python/frameworks/data/internal/empty.model.yml @@ -30,6 +30,28 @@ extensions: - ["flask.MethodView~Subclass","flask_restplus","Member[resource].Member[MethodView]"] - ["flask.MethodView~Subclass","flask_restplus","Member[Resource]"] + - ["flask.MethodView~Subclass","flask_restx","Member[api].Member[SwaggerView]"] + - ["flask.MethodView~Subclass","flask_restx","Member[resource].Member[Resource]"] + - ["flask.MethodView~Subclass","flask_restx","Member[api].Member[Resource]"] + - ["flask.MethodView~Subclass","flask_restx","Member[resource].Member[MethodView]"] + - ["flask.MethodView~Subclass","flask_restx","Member[Resource]"] + + - ["flask.MethodView~Subclass", "flask_restful", "Member[Resource]"] + + - ["fastapi.APIRouter~Subclass","fastapi_utils","Member[inferring_router].Member[InferringRouter]"] + - ["fastapi.APIRouter~Subclass","fastapi_utils","Member[inferring_router].Member[APIRouter]"] + - ["fastapi.APIRouter~Subclass","fastapi_utils","Member[cbv].Member[APIRouter]"] + + - ["django.forms.BaseForm~Subclass","haystack","Member[forms].Member[ModelSearchForm]"] + - ["django.forms.BaseForm~Subclass","haystack","Member[forms].Member[SearchForm]"] + - ["django.forms.BaseForm~Subclass","haystack","Member[forms].Member[FacetedSearchForm]"] + - ["django.forms.BaseForm~Subclass","haystack","Member[forms].Member[HighlightedSearchForm]"] + - ["django.forms.BaseForm~Subclass","haystack","Member[forms].Member[HighlightedModelSearchForm]"] + - ["django.forms.BaseForm~Subclass","haystack","Member[forms].Member[FacetedModelSearchForm]"] + - ["django.forms.BaseForm~Subclass","haystack","Member[views].Member[FacetedSearchForm]"] + - ["django.forms.BaseForm~Subclass","haystack","Member[views].Member[ModelSearchForm]"] + + - addsTo: pack: codeql/python-all extensible: typeVariableModel diff --git a/python/ql/src/meta/ClassHierarchy/Find.ql b/python/ql/src/meta/ClassHierarchy/Find.ql index 7067df844c1..fe6246e11eb 100644 --- a/python/ql/src/meta/ClassHierarchy/Find.ql +++ b/python/ql/src/meta/ClassHierarchy/Find.ql @@ -7,6 +7,14 @@ private import semmle.python.frameworks.FastApi private import semmle.python.frameworks.Django import semmle.python.frameworks.data.internal.ApiGraphModelsExtensions as Extensions +// FIXME: I think the implementation below for `getAlreadyModeledClass` is wrong, since +// it uses `.getASubclass*()` for flask/fastAPI (and initially also Django, I just fixed +// it for django while discovering this problem). Basically, I fear that it if library +// defines class A and B, where B is a subclass of A, the automated modeling might only +// find B... +// +// I doesn't seem to be the case, which is probably why I didn't discover this, but on +// top of my head I can't really tell why. class FlaskViewClasses extends FindSubclassesSpec { FlaskViewClasses() { this = "flask.View~Subclass" } @@ -21,6 +29,36 @@ class FlaskMethodViewClasses extends FindSubclassesSpec { override FlaskViewClasses getSuperClass() { any() } } +class FastApiRouter extends FindSubclassesSpec { + FastApiRouter() { this = "fastapi.APIRouter~Subclass" } + + override API::Node getAlreadyModeledClass() { result = FastApi::ApiRouter::cls() } +} + +class DjangoForms extends FindSubclassesSpec { + DjangoForms() { this = "django.forms.BaseForm~Subclass" } + + override API::Node getAlreadyModeledClass() { + result = any(Django::Forms::Form::ModeledSubclass subclass) + } +} + +class DjangoView extends FindSubclassesSpec { + DjangoView() { this = "Django.Views.View~Subclass" } + + override API::Node getAlreadyModeledClass() { + result = any(Django::Views::View::ModeledSubclass subclass) + } +} + +class DjangoField extends FindSubclassesSpec { + DjangoField() { this = "Django.Forms.Field~Subclass" } + + override API::Node getAlreadyModeledClass() { + result = any(Django::Forms::Field::ModeledSubclass subclass) + } +} + bindingset[fullyQualified] predicate fullyQualifiedToYamlFormat(string fullyQualified, string type2, string path) { exists(int firstDot | firstDot = fullyQualified.indexOf(".", 0, 0) |