mirror of
https://github.com/github/codeql.git
synced 2026-05-01 19:55:15 +02:00
Python: Add basic FastAPI support
This commit is contained in:
2
python/change-notes/2021-09-12-add-FastAPI-modeling.md
Normal file
2
python/change-notes/2021-09-12-add-FastAPI-modeling.md
Normal file
@@ -0,0 +1,2 @@
|
||||
lgtm,codescanning
|
||||
* Added modeling of sources/sinks when using FastAPI to create web servers.
|
||||
@@ -12,6 +12,7 @@ private import semmle.python.frameworks.Cryptography
|
||||
private import semmle.python.frameworks.Dill
|
||||
private import semmle.python.frameworks.Django
|
||||
private import semmle.python.frameworks.Fabric
|
||||
private import semmle.python.frameworks.FastApi
|
||||
private import semmle.python.frameworks.Flask
|
||||
private import semmle.python.frameworks.FlaskSqlAlchemy
|
||||
private import semmle.python.frameworks.Idna
|
||||
|
||||
75
python/ql/lib/semmle/python/frameworks/FastApi.qll
Normal file
75
python/ql/lib/semmle/python/frameworks/FastApi.qll
Normal file
@@ -0,0 +1,75 @@
|
||||
/**
|
||||
* Provides classes modeling security-relevant aspects of the `fastapi` PyPI package.
|
||||
* See https://fastapi.tiangolo.com/.
|
||||
*/
|
||||
|
||||
private import python
|
||||
private import semmle.python.dataflow.new.DataFlow
|
||||
private import semmle.python.dataflow.new.RemoteFlowSources
|
||||
private import semmle.python.dataflow.new.TaintTracking
|
||||
private import semmle.python.Concepts
|
||||
private import semmle.python.ApiGraphs
|
||||
|
||||
/**
|
||||
* Provides models for the `fastapi` PyPI package.
|
||||
* See https://fastapi.tiangolo.com/.
|
||||
*/
|
||||
private module FastApi {
|
||||
/**
|
||||
* Provides models for FastAPI applications (an instance of `fastapi.FastAPI`).
|
||||
*/
|
||||
module App {
|
||||
/** Gets a reference to a FastAPI application (an instance of `fastapi.FastAPI`). */
|
||||
API::Node instance() { result = API::moduleImport("fastapi").getMember("FastAPI").getReturn() }
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// routing modeling
|
||||
// ---------------------------------------------------------------------------
|
||||
/**
|
||||
* A call to a method like `get` or `post` on a FastAPI application.
|
||||
*
|
||||
* See https://fastapi.tiangolo.com/tutorial/first-steps/#define-a-path-operation-decorator
|
||||
*/
|
||||
private class FastApiRouteSetup extends HTTP::Server::RouteSetup::Range, DataFlow::CallCfgNode {
|
||||
FastApiRouteSetup() { this = App::instance().getMember(any(HTTP::httpVerbLower())).getACall() }
|
||||
|
||||
override Parameter getARoutedParameter() {
|
||||
// this will need to be refined a bit, since you can add special parameters to
|
||||
// your request handler functions that are used to pass in the response. There
|
||||
// might be other special cases as well, but as a start this is not too far off
|
||||
// the mark.
|
||||
result = this.getARequestHandler().getArgByName(_)
|
||||
}
|
||||
|
||||
override DataFlow::Node getUrlPatternArg() {
|
||||
result in [this.getArg(0), this.getArgByName("path")]
|
||||
}
|
||||
|
||||
override Function getARequestHandler() { result.getADecorator().getAFlowNode() = node }
|
||||
|
||||
override string getFramework() { result = "FastAPI" }
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Response modeling
|
||||
// ---------------------------------------------------------------------------
|
||||
/**
|
||||
* Implicit response from returns of FastAPI request handlers
|
||||
*/
|
||||
private class FastApiRequestHandlerReturn extends HTTP::Server::HttpResponse::Range,
|
||||
DataFlow::CfgNode {
|
||||
FastApiRequestHandlerReturn() {
|
||||
exists(Function requestHandler |
|
||||
requestHandler = any(FastApiRouteSetup rs).getARequestHandler() and
|
||||
node = requestHandler.getAReturnValueFlowNode()
|
||||
)
|
||||
}
|
||||
|
||||
override DataFlow::Node getBody() { result = this }
|
||||
|
||||
override DataFlow::Node getMimetypeOrContentTypeArg() { none() }
|
||||
|
||||
override string getMimetypeDefault() { result = "application/json" }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import python
|
||||
import experimental.meta.ConceptsTest
|
||||
|
||||
class DedicatedResponseTest extends HttpServerHttpResponseTest {
|
||||
DedicatedResponseTest() { file.getShortName() = "response_test.py" }
|
||||
}
|
||||
|
||||
class OtherResponseTest extends HttpServerHttpResponseTest {
|
||||
OtherResponseTest() { not this instanceof DedicatedResponseTest }
|
||||
|
||||
override string getARelevantTag() { result = "HttpResponse" }
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
argumentToEnsureNotTaintedNotMarkedAsSpurious
|
||||
untaintedArgumentToEnsureTaintedNotMarkedAsMissing
|
||||
failures
|
||||
@@ -0,0 +1 @@
|
||||
import experimental.meta.InlineTaintTest
|
||||
73
python/ql/test/library-tests/frameworks/fastapi/basic.py
Normal file
73
python/ql/test/library-tests/frameworks/fastapi/basic.py
Normal file
@@ -0,0 +1,73 @@
|
||||
# Taking inspiration from https://realpython.com/fastapi-python-web-apis/
|
||||
|
||||
# run with
|
||||
# uvicorn basic:app --reload
|
||||
# Then visit http://127.0.0.1:8000/docs and http://127.0.0.1:8000/redoc
|
||||
|
||||
from fastapi import FastAPI
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
@app.get("/") # $ routeSetup="/"
|
||||
async def root(): # $ requestHandler
|
||||
return {"message": "Hello World"} # $ HttpResponse
|
||||
|
||||
@app.get("/non-async") # $ routeSetup="/non-async"
|
||||
def non_async(): # $ requestHandler
|
||||
return {"message": "non-async"} # $ HttpResponse
|
||||
|
||||
@app.get(path="/kw-arg") # $ routeSetup="/kw-arg"
|
||||
def kw_arg(): # $ requestHandler
|
||||
return {"message": "kw arg"} # $ HttpResponse
|
||||
|
||||
@app.get("/foo/{foo_id}") # $ routeSetup="/foo/{foo_id}"
|
||||
async def get_foo(foo_id: int): # $ requestHandler routedParameter=foo_id
|
||||
# FastAPI does data validation (with `pydantic` PyPI package) under the hood based
|
||||
# on the type annotation we did for `foo_id`, so it will auto-reject anything that's
|
||||
# not an int.
|
||||
return {"foo_id": foo_id} # $ HttpResponse
|
||||
|
||||
# this will work as query param, so `/bar?bar_id=123`
|
||||
@app.get("/bar") # $ routeSetup="/bar"
|
||||
async def get_bar(bar_id: int = 42): # $ requestHandler routedParameter=bar_id
|
||||
return {"bar_id": bar_id} # $ HttpResponse
|
||||
|
||||
# The big deal is that FastAPI works so well together with pydantic, so you can do stuff like this
|
||||
from typing import Optional
|
||||
from pydantic import BaseModel
|
||||
|
||||
class Item(BaseModel):
|
||||
name: str
|
||||
price: float
|
||||
is_offer: Optional[bool] = None
|
||||
|
||||
@app.post("/items/") # $ routeSetup="/items/"
|
||||
async def create_item(item: Item): # $ requestHandler routedParameter=item
|
||||
# Note: calling `item` a routed parameter is slightly untrue, since it doesn't come
|
||||
# from the URL itself, but from the body of the POST request
|
||||
return item # $ HttpResponse
|
||||
|
||||
# this also works fine
|
||||
@app.post("/2items") # $ routeSetup="/2items"
|
||||
async def create_item2(item1: Item, item2: Item): # $ requestHandler routedParameter=item1 routedParameter=item2
|
||||
return (item1, item2) # $ HttpResponse
|
||||
|
||||
|
||||
|
||||
|
||||
# Docs:
|
||||
# see https://fastapi.tiangolo.com/tutorial/path-params/
|
||||
|
||||
# More stuff that we should support:
|
||||
# - https://fastapi.tiangolo.com/tutorial/bigger-applications/
|
||||
# - https://fastapi.tiangolo.com/advanced/response-cookies/
|
||||
# - https://fastapi.tiangolo.com/tutorial/dependencies/
|
||||
# - Extra taint-steps for files
|
||||
# - https://fastapi.tiangolo.com/tutorial/request-files/
|
||||
# - https://fastapi.tiangolo.com/tutorial/request-files/#uploadfile
|
||||
# - https://fastapi.tiangolo.com/tutorial/background-tasks/
|
||||
#
|
||||
# - https://fastapi.tiangolo.com/tutorial/middleware/
|
||||
# - https://fastapi.tiangolo.com/tutorial/encoder/
|
||||
#
|
||||
# - `app.route()`
|
||||
@@ -0,0 +1 @@
|
||||
# TODO: Add detailed tests of ways to create responses in this file.
|
||||
Reference in New Issue
Block a user