Skip to content

Commit

Permalink
Typing infrastructure improvements
Browse files Browse the repository at this point in the history
  • Loading branch information
parfeniukink committed Jan 28, 2024
1 parent cb86159 commit b51ce09
Show file tree
Hide file tree
Showing 29 changed files with 198 additions and 134 deletions.
8 changes: 5 additions & 3 deletions examples/fitbit.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
"""Fitbit Login Example
"""
"""Fitbit Login Example."""


import os

import uvicorn
from fastapi import FastAPI, Request
from fastapi_sso.sso.fitbit import FitbitSSO

from fastapi_sso import FitbitSSO

CLIENT_ID = os.environ["CLIENT_ID"]
CLIENT_SECRET = os.environ["CLIENT_SECRET"]
Expand Down
11 changes: 7 additions & 4 deletions examples/generic.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@
"""

from typing import Any, Dict, Union
from httpx import AsyncClient

import uvicorn
from fastapi import FastAPI, HTTPException
from httpx import AsyncClient
from starlette.requests import Request
from fastapi_sso.sso.base import DiscoveryDocument, OpenID
from fastapi_sso.sso.generic import create_provider

from fastapi_sso.infrastructure import DiscoveryDocument, OpenID, factories

app = FastAPI()

Expand Down Expand Up @@ -36,7 +37,9 @@ def convert_openid(response: Dict[str, Any], _client: Union[AsyncClient, None])
"userinfo_endpoint": "http://localhost:9090/me",
}

GenericSSO = create_provider(name="oidc", discovery_document=discovery_document, response_convertor=convert_openid)
GenericSSO = factories.create_provider(
name="oidc", discovery_document=discovery_document, response_convertor=convert_openid
)

sso = GenericSSO(
client_id="test", client_secret="secret", redirect_uri="http://localhost:8080/callback", allow_insecure_http=True
Expand Down
8 changes: 5 additions & 3 deletions examples/github.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
"""Github Login Example
"""
"""Github Login Example."""


import os

import uvicorn
from fastapi import FastAPI, Request
from fastapi_sso.sso.github import GithubSSO

from fastapi_sso import GithubSSO

CLIENT_ID = os.environ["CLIENT_ID"]
CLIENT_SECRET = os.environ["CLIENT_SECRET"]
Expand Down
8 changes: 5 additions & 3 deletions examples/gitlab.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
"""Github Login Example
"""
"""Github Login Example."""


import os

import uvicorn
from fastapi import FastAPI, Request
from fastapi_sso.sso.gitlab import GitlabSSO

from fastapi_sso import GitlabSSO

CLIENT_ID = os.environ["CLIENT_ID"]
CLIENT_SECRET = os.environ["CLIENT_SECRET"]
Expand Down
7 changes: 4 additions & 3 deletions examples/google.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
"""Google Login Example
"""
"""Google Login Example."""

import os

import uvicorn
from fastapi import FastAPI, Request
from fastapi_sso.sso.google import GoogleSSO

from fastapi_sso import GoogleSSO

CLIENT_ID = os.environ["CLIENT_ID"]
CLIENT_SECRET = os.environ["CLIENT_SECRET"]
Expand Down
7 changes: 4 additions & 3 deletions examples/kakao.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
"""Kakao Login Example
"""
"""Kakao Login Example."""

import os

import uvicorn
from fastapi import FastAPI, Request
from fastapi_sso.sso.kakao import KakaoSSO

from fastapi_sso import KakaoSSO

CLIENT_ID = os.environ["CLIENT_ID"]
CLIENT_SECRET = os.environ["CLIENT_SECRET"]
Expand Down
7 changes: 4 additions & 3 deletions examples/microsoft.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
"""Microsoft Login Example
"""
"""Microsoft Login Example."""

import os

import uvicorn
from fastapi import FastAPI, Request
from fastapi_sso.sso.microsoft import MicrosoftSSO

from fastapi_sso import MicrosoftSSO

CLIENT_ID = os.environ["CLIENT_ID"]
CLIENT_SECRET = os.environ["CLIENT_SECRET"]
Expand Down
7 changes: 4 additions & 3 deletions examples/naver.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
"""Naver Login Example
"""
"""Naver Login Example."""

import os

import uvicorn
from fastapi import FastAPI, Request
from fastapi_sso.sso.naver import NaverSSO

from fastapi_sso import NaverSSO

CLIENT_ID = os.environ["CLIENT_ID"]
CLIENT_SECRET = os.environ["CLIENT_SECRET"]
Expand Down
4 changes: 3 additions & 1 deletion examples/notion.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@
"""

import os

import uvicorn
from fastapi import FastAPI, Request
from fastapi_sso.sso.notion import NotionSSO

from fastapi_sso import NotionSSO

CLIENT_ID = os.environ["CLIENT_ID"]
CLIENT_SECRET = os.environ["CLIENT_SECRET"]
Expand Down
14 changes: 2 additions & 12 deletions fastapi_sso/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,5 @@
(such as Facebook login, Google login and login via Microsoft Office 365 account)
"""

from .sso.base import OpenID, SSOBase, SSOLoginError
from .sso.facebook import FacebookSSO
from .sso.fitbit import FitbitSSO
from .sso.generic import create_provider
from .sso.github import GithubSSO
from .sso.gitlab import GitlabSSO
from .sso.google import GoogleSSO
from .sso.kakao import KakaoSSO
from .sso.microsoft import MicrosoftSSO
from .sso.naver import NaverSSO
from .sso.notion import NotionSSO
from .sso.spotify import SpotifySSO
from .infrastructure import * # noqa: F401, F403

Check notice

Code scanning / CodeQL

'import *' may pollute namespace Note

Import pollutes the enclosing namespace, as the imported module
fastapi_sso.infrastructure
does not define '__all__'.
from .sso import * # noqa: F401, F403

Check notice

Code scanning / CodeQL

'import *' may pollute namespace Note

Import pollutes the enclosing namespace, as the imported module
fastapi_sso.sso
does not define '__all__'.
2 changes: 2 additions & 0 deletions fastapi_sso/infrastructure/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
from . import factories # noqa: F401
from .openid import * # noqa: F401, F403
1 change: 1 addition & 0 deletions fastapi_sso/infrastructure/factories/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .provider import * # noqa: F401, F403
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
"""A generic OAuth client that can be used to quickly create support for any OAuth provider
with close to no code
"""A generic OAuth client that can be used to quickly create support for any OAuth provider with close to no code.
"""

__all__ = ("create_provider",)

import logging
from typing import TYPE_CHECKING, Any, Callable, Dict, List, Optional, Type, Union
from typing import Any, Callable, Dict, List, Optional, Type, Union

from fastapi_sso.sso.base import DiscoveryDocument, OpenID, SSOBase
import httpx

Check notice

Code scanning / CodeQL

Unused import Note

Import of 'httpx' is not used.

if TYPE_CHECKING:
import httpx
from ..openid import DiscoveryDocument, OpenID, SSOBase

logger = logging.getLogger(__name__)

Expand All @@ -33,21 +33,22 @@ def create_provider(
Example:
```python
from fastapi_sso.sso.generic import create_provider
from fastapi_sso.infrastructure import factories
discovery = {
"authorization_endpoint": "http://localhost:9090/auth",
"token_endpoint": "http://localhost:9090/token",
"userinfo_endpoint": "http://localhost:9090/me",
}
SSOProvider = create_provider(name="oidc", discovery_document=discovery)
SSOProvider = factories.create_provider(name="oidc", discovery_document=discovery)
sso = SSOProvider(
client_id="test",
client_secret="secret",
redirect_uri="http://localhost:8080/callback",
allow_insecure_http=True
)
)
```
"""
Expand Down
11 changes: 11 additions & 0 deletions fastapi_sso/sso/base.py → fastapi_sso/infrastructure/openid.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,16 @@
"""
# pylint: disable=too-few-public-methods

__all__ = (
"OpenID",
"SSOBase",
"SSOLoginError",
"DiscoveryDocument",
"ReusedOauthClientWarning",
"UnsetStateWarning",
)


import json
import os
import sys
Expand Down Expand Up @@ -360,6 +370,7 @@ async def process_login(
headers.update(additional_headers)

auth = httpx.BasicAuth(self.client_id, self.client_secret)

async with httpx.AsyncClient() as session:
response = await session.post(token_url, headers=headers, content=body, auth=auth)
content = response.json()
Expand Down
10 changes: 10 additions & 0 deletions fastapi_sso/sso/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from .facebook import * # noqa: F401, F403
from .fitbit import * # noqa: F401, F403
from .github import * # noqa: F401, F403
from .gitlab import * # noqa: F401, F403
from .google import * # noqa: F401, F403
from .kakao import * # noqa: F401, F403
from .microsoft import * # noqa: F401, F403
from .naver import * # noqa: F401, F403
from .notion import * # noqa: F401, F403

Check notice

Code scanning / CodeQL

'import *' may pollute namespace Note

Import pollutes the enclosing namespace, as the imported module
fastapi_sso.sso.notion
does not define '__all__'.
from .spotify import * # noqa: F401, F403
16 changes: 10 additions & 6 deletions fastapi_sso/sso/facebook.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
"""Facebook SSO Login Helper
"""
"""Facebook SSO Login Helper."""

from typing import TYPE_CHECKING, Optional

from fastapi_sso.sso.base import DiscoveryDocument, OpenID, SSOBase
__all__ = ("FacebookSSO",)

if TYPE_CHECKING:
import httpx

from typing import Optional

import httpx

from fastapi_sso.infrastructure import DiscoveryDocument, OpenID, SSOBase


class FacebookSSO(SSOBase):
Expand All @@ -18,6 +20,7 @@ class FacebookSSO(SSOBase):

async def get_discovery_document(self) -> DiscoveryDocument:
"""Get document containing handy urls"""

return {
"authorization_endpoint": "https://www.facebook.com/v9.0/dialog/oauth",
"token_endpoint": f"{self.base_url}/oauth/access_token",
Expand All @@ -26,6 +29,7 @@ async def get_discovery_document(self) -> DiscoveryDocument:

async def openid_from_response(self, response: dict, session: Optional["httpx.AsyncClient"] = None) -> OpenID:
"""Return OpenID from user information provided by Facebook"""

return OpenID(
email=response.get("email", ""),
first_name=response.get("first_name"),
Expand Down
20 changes: 12 additions & 8 deletions fastapi_sso/sso/fitbit.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
"""Fitbit OAuth Login Helper
"""
"""Fitbit OAuth Login Helper."""

from typing import TYPE_CHECKING, Optional

from fastapi_sso.sso.base import DiscoveryDocument, OpenID, SSOBase, SSOLoginError
__all__ = ("FitbitSSO",)

if TYPE_CHECKING:
import httpx

from typing import Optional

import httpx

from fastapi_sso.infrastructure import DiscoveryDocument, OpenID, SSOBase, SSOLoginError


class FitbitSSO(SSOBase):
Expand All @@ -17,9 +19,10 @@ class FitbitSSO(SSOBase):

async def openid_from_response(self, response: dict, session: Optional["httpx.AsyncClient"] = None) -> OpenID:
"""Return OpenID from user information provided by Google"""
info = response.get("user")
if not info:

if not (info := response.get("user")):

Check warning on line 23 in fastapi_sso/sso/fitbit.py

View check run for this annotation

Codecov / codecov/patch

fastapi_sso/sso/fitbit.py#L23

Added line #L23 was not covered by tests
raise SSOLoginError(401, "Failed to process login via Fitbit")

return OpenID(
id=info["encodedId"],
first_name=info["fullName"],
Expand All @@ -30,6 +33,7 @@ async def openid_from_response(self, response: dict, session: Optional["httpx.As

async def get_discovery_document(self) -> DiscoveryDocument:
"""Get document containing handy urls"""

return {
"authorization_endpoint": "https://www.fitbit.com/oauth2/authorize?response_type=code",
"token_endpoint": "https://api.fitbit.com/oauth2/token",
Expand Down
19 changes: 14 additions & 5 deletions fastapi_sso/sso/github.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
"""Github SSO Oauth Helper class"""
"""Github SSO Oauth Helper class."""

from typing import TYPE_CHECKING, Optional

from fastapi_sso.sso.base import DiscoveryDocument, OpenID, SSOBase
__all__ = ("GithubSSO",)

if TYPE_CHECKING:
import httpx

from typing import Optional

import httpx

from fastapi_sso.infrastructure import DiscoveryDocument, OpenID, SSOBase


class GithubSSO(SSOBase):
Expand All @@ -26,15 +29,21 @@ async def get_discovery_document(self) -> DiscoveryDocument:
async def _get_primary_email(self, session: Optional["httpx.AsyncClient"] = None) -> Optional[str]:
"""Attempt to get primary email from Github for a current user.
The session received must be authenticated."""

if not session:
return None

response = await session.get(self.emails_endpoint)

if response.status_code != 200:
return None

emails = response.json()

for email in emails:
if email["primary"]:
return email["email"]

return None

async def openid_from_response(self, response: dict, session: Optional["httpx.AsyncClient"] = None) -> OpenID:
Expand Down
12 changes: 7 additions & 5 deletions fastapi_sso/sso/gitlab.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
"""Gitlab SSO Oauth Helper class"""
"""Gitlab SSO Oauth Helper class."""

from typing import TYPE_CHECKING, Optional

from fastapi_sso.sso.base import DiscoveryDocument, OpenID, SSOBase
__all__ = ("GitlabSSO",)

if TYPE_CHECKING:
import httpx
from typing import Optional

import httpx

from fastapi_sso.infrastructure import DiscoveryDocument, OpenID, SSOBase


class GitlabSSO(SSOBase):
Expand Down
Loading

0 comments on commit b51ce09

Please sign in to comment.