Neoteroi / BlackSheep

Fast ASGI web framework for Python
https://www.neoteroi.dev/blacksheep/
MIT License
1.8k stars 75 forks source link

Support security defenition in OpenAPI docs #349

Closed tyzhnenko closed 1 year ago

tyzhnenko commented 1 year ago

Add to APIDocsHandler call security parameter

    @docs(
        security=[
            SecurityInfo("basicAuth", []),
            SecurityInfo("bearerAuth", ["read:home", "write:home"]),
        ]
    )

Add to OpenAPIHandler constructor security_schemes parameter

    docs = OpenAPIHandler(
        info=Info(
            title="Example API",
            version="0.0.1",
        ),
        security_schemes={
            "oauth2": OAuth2Security(
                flows=OAuthFlows(
                    implicit=OAuthFlow(
                        authorization_url="https://example.com/oauth2/authorize",
                        token_url="https://example.com/oauth2/token",
                        refresh_url="https://example.com/oauth2/refresh",
                        scopes={
                            "read:cats": "Read your cats",
                            "write:cats": "Write your cats",
                        },
                    )
                ),
                description="OAuth2 Auth",
            ),
        },
    )

OpenAPI documentation - https://swagger.io/docs/specification/authentication/

codecov-commenter commented 1 year ago

Codecov Report

Patch coverage: 100.00% and no project coverage change.

Comparison is base (e152571) 97.70% compared to head (1d98b7b) 97.71%.

:mega: This organization is not using Codecov’s GitHub App Integration. We recommend you install it so Codecov can continue to function properly for your repositories. Learn more

Additional details and impacted files ```diff @@ Coverage Diff @@ ## main #349 +/- ## ======================================= Coverage 97.70% 97.71% ======================================= Files 66 66 Lines 6181 6201 +20 ======================================= + Hits 6039 6059 +20 Misses 142 142 ``` | [Impacted Files](https://app.codecov.io/gh/Neoteroi/BlackSheep/pull/349?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Neoteroi) | Coverage Δ | | |---|---|---| | [blacksheep/server/openapi/common.py](https://app.codecov.io/gh/Neoteroi/BlackSheep/pull/349?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Neoteroi#diff-YmxhY2tzaGVlcC9zZXJ2ZXIvb3BlbmFwaS9jb21tb24ucHk=) | `97.46% <100.00%> (+0.05%)` | :arrow_up: | | [blacksheep/server/openapi/v3.py](https://app.codecov.io/gh/Neoteroi/BlackSheep/pull/349?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Neoteroi#diff-YmxhY2tzaGVlcC9zZXJ2ZXIvb3BlbmFwaS92My5weQ==) | `86.93% <100.00%> (+0.41%)` | :arrow_up: |

:umbrella: View full report in Codecov by Sentry.
:loudspeaker: Do you have feedback about the report comment? Let us know in this issue.

tyzhnenko commented 1 year ago

@RobertoPrevato I've faced that BlackSheep can't produce OpenAPI documentation with a security description. As a result Swagger UI can't make requests for endpoints that require auth.

The PR contains improvements for the OpenAPI documentation generator to handle this problem.

RobertoPrevato commented 1 year ago

Hi @tyzhnenko Thank You for helping with this. This is something I should have documented better, having time. I didn´t document it more just because time is limited and I need to prioritize.

This is mentioned too briefly in the documentation, here https://www.neoteroi.dev/blacksheep/openapi/#altering-the-specification-upon-creation but it is possible to edit the OpenAPI Specification upon creation, to do pretty much anything with it.

For example, to configure security:

from blacksheep import Application
from blacksheep.server.openapi.v3 import OpenAPIHandler
from openapidocs.v3 import Info, OpenAPI, Security, SecurityRequirement, HTTPSecurity

app = Application()

class MyOpenAPIHandler(OpenAPIHandler):
    def on_docs_generated(self, docs: OpenAPI) -> None:
        docs.security = Security([SecurityRequirement("basic", [])])
        assert docs.components is not None
        docs.components.security_schemes = {"basic_auth": HTTPSecurity("basic")}

docs = MyOpenAPIHandler(info=Info(title="Example API", version="0.0.1"))
docs.bind_app(app)

@app.route("/")
async def hello():
    return "Hello World!"

if __name__ == "__main__":
    import uvicorn

    uvicorn.run(app, port=44555)

image

image

OpenAPI Specification is vast and there are so many options that not everything should be covered within the OpenAPIHandler constructor. The main responsibility of this class is to help with whatever can be generated automatically using the information that is available to the web framework. Then the classes in essentials-openapi can be used to describe a specification and generate YAML / JSON, and polished using the on_docs_generated method.

I try to review your PR tomorrow - I didn´t have time today.

tyzhnenko commented 1 year ago

Hi, @RobertoPrevato

I see your points.

I wanted to make this automatically just using auth attribute of handlers but I didn't find a way unfortunately. Due OpenAPI schema needs a name and scopes for an operator security field we can't use just handler.auth to fill everything automatically.

It makes sense when some endpoints require auth and some can be called anonymously.

RobertoPrevato commented 1 year ago

I wanted to make this automatically just using auth attribute of handlers but I didn't find a way unfortunately. Due OpenAPI schema needs a name and scopes for an operator security field we can't use just handler.auth to fill everything automatically.

Absolutely, I wanted to do the same, but it's a complex topic and it would require a big amount of time to generate auth specs automatically. Not even web frameworks baked by big team working for big corporations offer this feature. Scopes are not always needed because they are only for oauth2 security, but it's anyway challenging to cover all scenarios (basic and API Key would be relatively easy). Another challenge when you use OAuth2, is that the Swagger UI has a dedicated client app registration which is not the same as the app registration used by the web API being documented. There is also the need to keep the web framework itself abstracted away and agnostic of a specific way of documenting APIs, consider:

Both OpenAPI and AsyncAPI are part of the Linux Foundation so they are also both equally legitimate citizens.

It makes sense when some endpoints require auth and some can be called anonymously.

True.

techntools commented 1 year ago

I am gunning for api first approach. Please check this discussion.

RobertoPrevato commented 1 year ago

I am gunning for api first approach. Please check this discussion.

I already apply an API First approach to my web apps and dedicated a good deal of time supporting automatic generation of OpenAPI Documentation in blacksheep. Time is limited and it's not possible to cover all possible scenarios, though. In the last days for example I started working on a blacksheep-cli and project templates to support bootstrapping new projects faster + configuration strategy.

techntools commented 1 year ago

You are right that time is limited.

I think spec first instead of api first is the right choice of words for what i was trying to say.

But can not cover all scenarios and framework does offer nice ways to cover scenarios not built into it.

techntools commented 1 year ago

Would the cli support management command like django does ? I find them useful.

RobertoPrevato commented 1 year ago

@tyzhnenko I love this! You coded in the same style I would have used, nothing comes to my head right now! ❤️ Sorry for taking so much time to review, I am on vacation this week and I am dedicating more time to my family.