Skip to content

Commit

Permalink
Adds a "tox -e type" and it largely works
Browse files Browse the repository at this point in the history
  • Loading branch information
rayluo committed Oct 13, 2024
1 parent 264c773 commit f9403e4
Show file tree
Hide file tree
Showing 6 changed files with 63 additions and 29 deletions.
8 changes: 4 additions & 4 deletions identity/django.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from functools import partial, wraps
import logging
import os
from typing import List # Needed in Python 3.7 & 3.8
from typing import List, Optional # Needed in Python 3.7 & 3.8
from urllib.parse import urlparse

from django.shortcuts import redirect, render
Expand Down Expand Up @@ -50,9 +50,9 @@ def __init__(self, *args, **kwargs):
def login(
self,
request,
next_link:str = None, # Obtain the next_link from the app developer,
next_link: Optional[str] = None, # Obtain the next_link from the app developer,
# NOT from query string which could become an open redirect vulnerability.
scopes: List[str]=None,
scopes: Optional[List[str]] = None,
):
# The login view.
# App developer could redirect to the login page from inside a view,
Expand Down Expand Up @@ -117,7 +117,7 @@ def login_required( # Named after Django's login_required
function=None,
/, # Requires Python 3.8+
*,
scopes: List[str]=None,
scopes: Optional[List[str]] = None,
):
"""A decorator that ensures the user to be logged in,
and optinoally also have consented to a list of scopes.
Expand Down
16 changes: 12 additions & 4 deletions identity/flask.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,20 +62,28 @@ def build_app():
self._session = session # Not available during class definition
super(Auth, self).__init__(app, *args, **kwargs)

def _render_auth_error(self, *, error, error_description=None):
def _render_auth_error( # type: ignore[override]
self, *, error, error_description=None,
) -> str:
return render_template(
f"{self._endpoint_prefix}/auth_error.html",
error=error,
error_description=error_description,
reset_password_url=self._get_reset_password_url(),
)

def login(self, *, next_link: str=None, scopes: List[str]=None):
def login(
self,
*,
next_link: Optional[str] = None,
scopes: Optional[List[str]] = None,
) -> str:
config_error = self._get_configuration_error()
if config_error:
return self._render_auth_error(
error="configuration_error", error_description=config_error)
log_in_result = self._auth.log_in(
assert self._auth, "_auth should have been initialized" # And mypy needs this
log_in_result: dict = self._auth.log_in(
scopes=scopes, # Have user consent to scopes (if any) during log-in
redirect_uri=self._redirect_uri,
prompt="select_account", # Optional. More values defined in https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest
Expand Down Expand Up @@ -106,7 +114,7 @@ def login_required( # Named after Django's login_required
function=None,
/, # Requires Python 3.8+
*,
scopes: List[str]=None,
scopes: Optional[List[str]] = None,
):
"""A decorator that ensures the user to be logged in,
and optinoally also have consented to a list of scopes.
Expand Down
8 changes: 4 additions & 4 deletions identity/pallet.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,17 @@
from inspect import iscoroutinefunction
import logging
import os
from typing import List # Needed in Python 3.7 & 3.8
from typing import List, Optional # Needed in Python 3.7 & 3.8
from urllib.parse import urlparse
from .web import WebFrameworkAuth
from .web import WebFrameworkAuth, Auth


logger = logging.getLogger(__name__)


class PalletAuth(WebFrameworkAuth): # A common base class for Flask and Quart
_endpoint_prefix = "identity" # A convention to match the template's folder name
_auth = None # None means not initialized yet
_auth: Optional[Auth] = None # None means not initialized yet

def __init__(self, app, *args, **kwargs):
if not (
Expand Down Expand Up @@ -71,7 +71,7 @@ def login_required( # Named after Django's login_required
function=None,
/, # Requires Python 3.8+
*,
scopes: List[str]=None,
scopes: Optional[List[str]] = None,
):
# With or without brackets. Inspired by https://stackoverflow.com/a/39335652/728675

Expand Down
10 changes: 8 additions & 2 deletions identity/quart.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,11 +70,17 @@ async def _render_auth_error(self, *, error, error_description=None):
reset_password_url=self._get_reset_password_url(),
)

async def login(self, *, next_link: str=None, scopes: List[str]=None):
async def login(
self,
*,
next_link: Optional[str] = None,
scopes: Optional[List[str]] = None,
):
config_error = self._get_configuration_error()
if config_error:
return await self._render_auth_error(
error="configuration_error", error_description=config_error)
assert self._auth, "_auth should have been initialized" # And mypy needs this
log_in_result = self._auth.log_in(
scopes=scopes, # Have user consent to scopes (if any) during log-in
redirect_uri=self._redirect_uri,
Expand Down Expand Up @@ -106,7 +112,7 @@ def login_required( # Named after Django's login_required
function=None,
/, # Requires Python 3.8+
*,
scopes: List[str]=None,
scopes: Optional[List[str]] = None,
):
"""A decorator that ensures the user to be logged in,
and optinoally also have consented to a list of scopes.
Expand Down
38 changes: 23 additions & 15 deletions identity/web.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import functools
import logging
import time
from typing import List # Needed in Python 3.7 & 3.8
from typing import List, Optional # Needed in Python 3.7 & 3.8

import requests
import msal
Expand Down Expand Up @@ -97,9 +97,14 @@ def _save_user_into_session(self, id_token_claims):
self._session[self._USER] = id_token_claims

def log_in(
self, scopes=None, redirect_uri=None, state=None, prompt=None,
next_link=None,
):
self,
*,
scopes: Optional[List[str]] = None,
redirect_uri: Optional[str] = None,
state: Optional[str] = None,
prompt: Optional[str] = None,
next_link: Optional[str] = None,
) -> dict:
"""This is the first leg of the authentication/authorization.
:param list scopes:
Expand Down Expand Up @@ -162,7 +167,7 @@ def log_in(
"user_code": flow["user_code"],
}

def complete_log_in(self, auth_response=None):
def complete_log_in(self, auth_response: Optional[dict] = None) -> dict:
"""This is the second leg of the authentication/authorization.
It is used inside your redirect_uri controller.
Expand Down Expand Up @@ -356,15 +361,15 @@ def __init__(
client_id: str,
*,
client_credential=None,
oidc_authority: str=None,
authority: str=None,
redirect_uri: str=None,
oidc_authority: Optional[str] = None,
authority: Optional[str] = None,
redirect_uri: Optional[str] = None,
# We end up accepting Microsoft Entra ID B2C parameters rather than generic urls
# because it is troublesome to build those urls in settings.py or templates
b2c_tenant_name: str=None,
b2c_signup_signin_user_flow: str=None,
b2c_edit_profile_user_flow: str=None,
b2c_reset_password_user_flow: str=None,
b2c_tenant_name: Optional[str] = None,
b2c_signup_signin_user_flow: Optional[str] = None,
b2c_edit_profile_user_flow: Optional[str] = None,
b2c_reset_password_user_flow: Optional[str] = None,
):
"""Create an identity helper for a web application.
Expand Down Expand Up @@ -419,8 +424,9 @@ def __init__(
self._client_id = client_id
self._client_credential = client_credential
self._redirect_uri = redirect_uri
self._http_cache = {} # All subsequent Auth instances will share this
self._http_cache: dict = {} # All subsequent Auth instances will share this

self._authority: Optional[str] = None # It makes mypy happy
# Note: We do not use overload, because we want to allow the caller to
# have only one code path that relay in all the optional parameters.
if b2c_tenant_name and b2c_signup_signin_user_flow:
Expand Down Expand Up @@ -467,7 +473,7 @@ def _get_configuration_error(self):
(2.3) the B2C_TENANT_NAME and SIGNUPSIGNIN_USER_FLOW pair?
"""

def _build_auth(self, session):
def _build_auth(self, session) -> Auth:
return Auth(
session=session,
oidc_authority=self._oidc_authority,
Expand Down Expand Up @@ -523,7 +529,9 @@ def _get_reset_password_url(self):
)["auth_uri"] if self._reset_password_auth and self._redirect_uri else None

@abstractmethod
def _render_auth_error(error, *, error_description=None):
def _render_auth_error(
error, *, error_description=None,
): # Return value could be a str, or a framework-specific Response object
# The default auth_error.html template may or may not escape.
# If a web framework does not escape it by default, a subclass shall escape it.
pass
12 changes: 12 additions & 0 deletions tox.ini
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
[tox]
# So the following name can be referenced in other sections such as testenv:type
x_project_name = identity

env_list =
py3
minversion = 4.21.2
Expand All @@ -13,3 +16,12 @@ deps =
commands =
pip list
pytest {tty:--color=yes} -o asyncio_default_fixture_loop_scope=function -rA {posargs}

[testenv:type]
deps =
-r requirements.txt
mypy
commands =
mypy --install-types --non-interactive
mypy {[tox]x_project_name}

0 comments on commit f9403e4

Please sign in to comment.