Skip to content

Commit

Permalink
Add security schemes to openapi
Browse files Browse the repository at this point in the history
  • Loading branch information
tarsil committed Oct 5, 2023
1 parent eac77d4 commit 54b5a97
Show file tree
Hide file tree
Showing 12 changed files with 289 additions and 33 deletions.
2 changes: 1 addition & 1 deletion esmerald/contrib/auth/common/middleware.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ async def authenticate(self, request: HTTPConnection) -> AuthResult:
auth_token = token_partition[-1]

if token_type not in self.config.auth_header_types:
raise NotAuthorized(detail=f"{token_type} is not an authorized header type")
raise NotAuthorized(detail=f"'{token_type}' is not an authorized header.")

try:
token = Token.decode(
Expand Down
25 changes: 13 additions & 12 deletions esmerald/openapi/enums.py
Original file line number Diff line number Diff line change
@@ -1,26 +1,27 @@
from enum import Enum


class SecuritySchemeType(str, Enum):
apiKey = "apiKey"
http = "http"
oauth2 = "oauth2"
openIdConnect = "openIdConnect"

class BaseEnum(str, Enum):
def __str__(self) -> str:
return self.value
return self.value # type: ignore

def __repr__(self) -> str:
return str(self)


class APIKeyIn(str, Enum):
class SecuritySchemeType(BaseEnum):
apiKey = "apiKey"
http = "http"
oauth2 = "oauth2"
mutualTLS = "mutualTLS"
openIdConnect = "openIdConnect"


class APIKeyIn(BaseEnum):
query = "query"
header = "header"
cookie = "cookie"

def __str__(self) -> str:
return self.value

def __repr__(self) -> str:
return str(self)
class Header(BaseEnum):
authorization = "Authorization"
3 changes: 2 additions & 1 deletion esmerald/openapi/openapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ def get_flat_params(route: Union[router.HTTPHandler, Any]) -> List[Any]:

def get_openapi_security_schemes(schemes: Any) -> Tuple[dict, list]:
"""
Builds the security schemes.di
Builds the security schemas for OpenAPI.
"""
security_definitions = {}
operation_security = []
Expand All @@ -71,6 +71,7 @@ def get_openapi_security_schemes(schemes: Any) -> Tuple[dict, list]:
security_name = security_requirement.scheme_name
security_definitions[security_name] = security_definition
operation_security.append({security_name: security_requirement})

return security_definitions, operation_security


Expand Down
7 changes: 7 additions & 0 deletions esmerald/openapi/security/api_key/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from .base import APIKeyInCookie, APIKeyInHeader, APIKeyInQuery

__all__ = [
"APIKeyInCookie",
"APIKeyInHeader",
"APIKeyInQuery",
]
73 changes: 73 additions & 0 deletions esmerald/openapi/security/api_key/base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
from typing import Any, Literal, Optional

from esmerald.openapi.enums import APIKeyIn, SecuritySchemeType
from esmerald.openapi.security.base import HTTPBase


class APIKeyInQuery(HTTPBase):
def __init__(
self,
*,
type_: Literal[
"apiKey", "http", "mutualTLS", "oauth2", "openIdConnect"
] = SecuritySchemeType.apiKey.value,
scheme_name: Optional[str] = None,
description: Optional[str] = None,
in_: Optional[Literal["query", "header", "cookie"]] = APIKeyIn.query.value,
name: Optional[str] = None,
**kwargs: Any,
):
super().__init__(
type_=type_,
description=description,
name=name,
in_=in_,
scheme_name=scheme_name or self.__class__.__name__,
**kwargs,
)


class APIKeyInHeader(HTTPBase):
def __init__(
self,
*,
type_: Literal[
"apiKey", "http", "mutualTLS", "oauth2", "openIdConnect"
] = SecuritySchemeType.apiKey.value,
scheme_name: Optional[str] = None,
description: Optional[str] = None,
in_: Optional[Literal["query", "header", "cookie"]] = APIKeyIn.header.value,
name: Optional[str] = None,
**kwargs: Any,
):
super().__init__(
type_=type_,
description=description,
name=name,
in_=in_,
scheme_name=scheme_name or self.__class__.__name__,
**kwargs,
)


class APIKeyInCookie(HTTPBase):
def __init__(
self,
*,
type_: Literal[
"apiKey", "http", "mutualTLS", "oauth2", "openIdConnect"
] = SecuritySchemeType.apiKey.value,
scheme_name: Optional[str] = None,
description: Optional[str] = None,
in_: Optional[Literal["query", "header", "cookie"]] = APIKeyIn.cookie.value,
name: Optional[str] = None,
**kwargs: Any,
):
super().__init__(
type_=type_,
description=description,
name=name,
in_=in_,
scheme_name=scheme_name or self.__class__.__name__,
**kwargs,
)
43 changes: 43 additions & 0 deletions esmerald/openapi/security/base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
from typing import Any, Literal, Optional, Union

from pydantic import AnyUrl, BaseModel, ConfigDict

from esmerald.openapi.models import SecurityScheme


class HTTPAuthorizationCredentials(BaseModel):
scheme: str
credentials: str


class HTTPBase(SecurityScheme):
"""
Base for all HTTP security headers.
"""

model_config = ConfigDict(extra="allow", arbitrary_types_allowed=True)

def __init__(
self,
*,
type_: Optional[Literal["apiKey", "http", "mutualTLS", "oauth2", "openIdConnect"]] = None,
bearerFormat: Optional[str] = None,
scheme_name: Optional[str] = None,
description: Optional[str] = None,
in_: Optional[Literal["query", "header", "cookie"]] = None,
name: Optional[str] = None,
scheme: Optional[str] = None,
openIdConnectUrl: Optional[Union[AnyUrl, str]] = None,
**kwargs: Any,
):
super().__init__( # type: ignore
type=type_,
bearerFormat=bearerFormat,
description=description,
name=name,
security_scheme_in=in_,
scheme_name=scheme_name,
scheme=scheme,
openIdConnectUrl=openIdConnectUrl,
**kwargs,
)
11 changes: 7 additions & 4 deletions esmerald/openapi/security/http/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
# from .base import HTTPBasic as HTTPBasic
# from .base import HTTPAuthorizationCredentials as HTTPAuthorizationCredentials
# from .base import HTTPBasicCredentials as HTTPBasicCredentials
from .base import Bearer as Bearer
from .base import Basic, Bearer, Digest

__all__ = [
"Basic",
"Bearer",
"Digest",
]
74 changes: 59 additions & 15 deletions esmerald/openapi/security/http/base.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,64 @@
from typing import Any, Literal, Optional

from pydantic import BaseModel, ConfigDict
from esmerald.openapi.enums import APIKeyIn, Header, SecuritySchemeType
from esmerald.openapi.security.base import HTTPBase

from esmerald.openapi.enums import APIKeyIn, SecuritySchemeType
from esmerald.openapi.models import SecurityScheme

class Basic(HTTPBase):
def __init__(
self,
*,
type_: Literal[
"apiKey", "http", "mutualTLS", "oauth2", "openIdConnect"
] = SecuritySchemeType.http.value,
bearerFormat: Optional[str] = None,
scheme_name: Optional[str] = None,
description: Optional[str] = None,
in_: Optional[Literal["query", "header", "cookie"]] = APIKeyIn.header.value,
name: Optional[str] = None,
scheme: Optional[str] = None,
**kwargs: Any,
):
super().__init__(
type_=type_,
bearerFormat=bearerFormat,
description=description,
name=name or "Basic",
in_=in_,
scheme=scheme or "basic",
scheme_name=scheme_name or self.__class__.__name__,
**kwargs,
)

class HTTPAuthorizationCredentials(BaseModel):
scheme: str
credentials: str

class Bearer(HTTPBase):
def __init__(
self,
*,
type_: Literal[
"apiKey", "http", "mutualTLS", "oauth2", "openIdConnect"
] = SecuritySchemeType.http.value,
bearerFormat: Optional[str] = None,
scheme_name: Optional[str] = None,
description: Optional[str] = None,
in_: Optional[Literal["query", "header", "cookie"]] = APIKeyIn.header.value,
name: Optional[str] = None,
scheme: Optional[str] = None,
**kwargs: Any,
):
super().__init__(
type_=type_,
bearerFormat=bearerFormat,
description=description,
name=name or Header.authorization,
in_=in_,
scheme=scheme or "bearer",
scheme_name=scheme_name or self.__class__.__name__,
**kwargs,
)

class Bearer(SecurityScheme):
model_config = ConfigDict(extra="allow", arbitrary_types_allowed=True)

class Digest(HTTPBase):
def __init__(
self,
*,
Expand All @@ -29,13 +74,12 @@ def __init__(
**kwargs: Any,
):
super().__init__(
type=type_,
description=description,
type_=type_,
bearerFormat=bearerFormat,
name=name,
description=description,
name=name or Header.authorization,
in_=in_,
scheme=scheme or "digest",
scheme_name=scheme_name or self.__class__.__name__,
**kwargs,
)
self.scheme_name = scheme_name or self.__class__.__name__
self.security_scheme_in = in_
self.name = name or "Authorization"
self.scheme = scheme or "bearer"
5 changes: 5 additions & 0 deletions esmerald/openapi/security/oauth2/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from .base import OAuth2

__all__ = [
"OAuth2",
]
49 changes: 49 additions & 0 deletions esmerald/openapi/security/oauth2/base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
from typing import Any, Dict, Literal, Optional, Union

from esmerald.openapi.enums import SecuritySchemeType
from esmerald.openapi.models import OAuthFlows
from esmerald.openapi.security.base import HTTPBase


class OAuth2(HTTPBase):
"""
The OAuth2 scheme.
For every parameter of the OAuthFlows, expects a OAuthFlow object type.
Example:
implicit: Optional[OAuthFlow] = OAuthFlow()
password: Optional[OAuthFlow] = OAuthFlow()
clientCredentials: Optional[OAuthFlow] = OAuthFlow()
authorizationCode: Optional[OAuthFlow] = OAuthFlow()
flows: OAuthFlows(
implicit=implicit,
password=password,
clientCredentials=clientCredentials,
authorizationCode=authorizationCode,
)
"""

def __init__(
self,
*,
type_: Literal[
"apiKey", "http", "mutualTLS", "oauth2", "openIdConnect"
] = SecuritySchemeType.oauth2.value,
scheme_name: Optional[str] = None,
description: Optional[str] = None,
name: Optional[str] = None,
flows: Union[OAuthFlows, Dict[str, Dict[str, Any]]] = OAuthFlows(),
**kwargs: Any,
):
extra: Dict[Any, Any] = {}
extra["flows"] = flows
extra.update(kwargs)
super().__init__(
type_=type_,
description=description,
name=name,
scheme_name=scheme_name or self.__class__.__name__,
**extra,
)
3 changes: 3 additions & 0 deletions esmerald/openapi/security/openid_connect/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from .base import OpenIdConnect

__all__ = ["OpenIdConnect"]
27 changes: 27 additions & 0 deletions esmerald/openapi/security/openid_connect/base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
from typing import Any, Literal, Optional, Union

from pydantic import AnyUrl

from esmerald.openapi.enums import SecuritySchemeType
from esmerald.openapi.security.base import HTTPBase


class OpenIdConnect(HTTPBase):
def __init__(
self,
*,
type_: Literal[
"apiKey", "http", "mutualTLS", "oauth2", "openIdConnect"
] = SecuritySchemeType.openIdConnect.value,
openIdConnectUrl: Optional[Union[AnyUrl, str]] = None,
scheme_name: Optional[str] = None,
description: Optional[str] = None,
**kwargs: Any,
):
super().__init__(
type_=type_,
description=description,
scheme_name=scheme_name or self.__class__.__name__,
openIdConnectUrl=openIdConnectUrl,
**kwargs,
)

0 comments on commit 54b5a97

Please sign in to comment.