Add type annotations.
This commit is contained in:
@@ -1,4 +1,8 @@
|
||||
import abc
|
||||
from enum import Enum
|
||||
from typing import Any, Optional, Union
|
||||
|
||||
from aiohttp import web
|
||||
|
||||
# see http://plope.com/pyramid_auth_design_api_postmortem
|
||||
|
||||
@@ -6,13 +10,14 @@ import abc
|
||||
class AbstractIdentityPolicy(metaclass=abc.ABCMeta):
|
||||
|
||||
@abc.abstractmethod
|
||||
async def identify(self, request):
|
||||
async def identify(self, request: web.Request) -> Optional[str]:
|
||||
"""Return the claimed identity of the user associated request or
|
||||
``None`` if no identity can be found associated with the request."""
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
async def remember(self, request, response, identity, **kwargs):
|
||||
async def remember(self, request: web.Request, response: web.StreamResponse,
|
||||
identity: str, **kwargs: Any) -> None:
|
||||
"""Remember identity.
|
||||
|
||||
Modify response object by filling it's headers with remembered user.
|
||||
@@ -23,7 +28,7 @@ class AbstractIdentityPolicy(metaclass=abc.ABCMeta):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
async def forget(self, request, response):
|
||||
async def forget(self, request: web.Request, response: web.StreamResponse) -> None:
|
||||
""" Modify response which can be used to 'forget' the
|
||||
current identity on subsequent requests."""
|
||||
pass
|
||||
@@ -32,7 +37,8 @@ class AbstractIdentityPolicy(metaclass=abc.ABCMeta):
|
||||
class AbstractAuthorizationPolicy(metaclass=abc.ABCMeta):
|
||||
|
||||
@abc.abstractmethod
|
||||
async def permits(self, identity, permission, context=None):
|
||||
async def permits(self, identity: str, permission: Union[str, Enum],
|
||||
context: Any = None) -> bool:
|
||||
"""Check user permissions.
|
||||
|
||||
Return True if the identity is allowed the permission in the
|
||||
@@ -41,7 +47,7 @@ class AbstractAuthorizationPolicy(metaclass=abc.ABCMeta):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
async def authorized_userid(self, identity):
|
||||
async def authorized_userid(self, identity: str) -> Optional[str]:
|
||||
"""Retrieve authorized user id.
|
||||
|
||||
Return the user_id of the user identified by the identity
|
||||
|
@@ -1,15 +1,23 @@
|
||||
import enum
|
||||
import warnings
|
||||
from aiohttp import web
|
||||
from aiohttp_security.abc import (AbstractIdentityPolicy,
|
||||
AbstractAuthorizationPolicy)
|
||||
from functools import wraps
|
||||
from typing import Any, Callable, Optional, TypeVar, Union
|
||||
|
||||
from aiohttp import web
|
||||
from aiohttp_security.abc import AbstractAuthorizationPolicy, AbstractIdentityPolicy
|
||||
|
||||
IDENTITY_KEY = 'aiohttp_security_identity_policy'
|
||||
AUTZ_KEY = 'aiohttp_security_autz_policy'
|
||||
|
||||
# _AIP/_AAP are shorthand for Optional[policy] when we retrieve from request.
|
||||
_AAP = Optional[AbstractAuthorizationPolicy]
|
||||
_AIP = Optional[AbstractIdentityPolicy]
|
||||
_Handler = TypeVar('_Handler', bound=Union[Callable[[web.Request], Any],
|
||||
Callable[[object, web.Request], Any]])
|
||||
|
||||
async def remember(request, response, identity, **kwargs):
|
||||
|
||||
async def remember(request: web.Request, response: web.StreamResponse,
|
||||
identity: str, **kwargs: Any) -> None:
|
||||
"""Remember identity into response.
|
||||
|
||||
The action is performed by identity_policy.remember()
|
||||
@@ -30,7 +38,7 @@ async def remember(request, response, identity, **kwargs):
|
||||
await identity_policy.remember(request, response, identity, **kwargs)
|
||||
|
||||
|
||||
async def forget(request, response):
|
||||
async def forget(request: web.Request, response: web.StreamResponse) -> None:
|
||||
"""Forget previously remembered identity.
|
||||
|
||||
Usually it clears cookie or server-side storage to forget user
|
||||
@@ -47,9 +55,9 @@ async def forget(request, response):
|
||||
await identity_policy.forget(request, response)
|
||||
|
||||
|
||||
async def authorized_userid(request):
|
||||
identity_policy = request.config_dict.get(IDENTITY_KEY)
|
||||
autz_policy = request.config_dict.get(AUTZ_KEY)
|
||||
async def authorized_userid(request: web.Request) -> Optional[str]:
|
||||
identity_policy: _AIP = request.config_dict.get(IDENTITY_KEY)
|
||||
autz_policy: _AAP = request.config_dict.get(AUTZ_KEY)
|
||||
if identity_policy is None or autz_policy is None:
|
||||
return None
|
||||
identity = await identity_policy.identify(request)
|
||||
@@ -59,20 +67,21 @@ async def authorized_userid(request):
|
||||
return user_id
|
||||
|
||||
|
||||
async def permits(request, permission, context=None):
|
||||
async def permits(request: web.Request, permission: Union[str, enum.Enum],
|
||||
context: Any = None) -> bool:
|
||||
assert isinstance(permission, (str, enum.Enum)), permission
|
||||
assert permission
|
||||
identity_policy = request.config_dict.get(IDENTITY_KEY)
|
||||
autz_policy = request.config_dict.get(AUTZ_KEY)
|
||||
identity_policy: _AIP = request.config_dict.get(IDENTITY_KEY)
|
||||
autz_policy: _AAP = request.config_dict.get(AUTZ_KEY)
|
||||
if identity_policy is None or autz_policy is None:
|
||||
return True
|
||||
identity = await identity_policy.identify(request)
|
||||
# non-registered user still may has some permissions
|
||||
# non-registered user still may have some permissions
|
||||
access = await autz_policy.permits(identity, permission, context)
|
||||
return access
|
||||
|
||||
|
||||
async def is_anonymous(request):
|
||||
async def is_anonymous(request: web.Request) -> bool:
|
||||
"""Check if user is anonymous.
|
||||
|
||||
User is considered anonymous if there is not identity
|
||||
@@ -87,7 +96,7 @@ async def is_anonymous(request):
|
||||
return False
|
||||
|
||||
|
||||
async def check_authorized(request):
|
||||
async def check_authorized(request: web.Request) -> str:
|
||||
"""Checker that raises HTTPUnauthorized for anonymous users.
|
||||
"""
|
||||
userid = await authorized_userid(request)
|
||||
@@ -96,31 +105,32 @@ async def check_authorized(request):
|
||||
return userid
|
||||
|
||||
|
||||
def login_required(fn):
|
||||
def login_required(fn: _Handler) -> _Handler:
|
||||
"""Decorator that restrict access only for authorized users.
|
||||
|
||||
User is considered authorized if authorized_userid
|
||||
returns some value.
|
||||
"""
|
||||
@wraps(fn)
|
||||
async def wrapped(*args, **kwargs):
|
||||
async def wrapped(*args: Union[object, web.Request]) -> Any:
|
||||
request = args[-1]
|
||||
if not isinstance(request, web.BaseRequest):
|
||||
if not isinstance(request, web.Request):
|
||||
msg = ("Incorrect decorator usage. "
|
||||
"Expecting `def handler(request)` "
|
||||
"or `def handler(self, request)`.")
|
||||
raise RuntimeError(msg)
|
||||
|
||||
await check_authorized(request)
|
||||
return await fn(*args, **kwargs)
|
||||
return await fn(*args) # type: ignore[arg-type]
|
||||
|
||||
warnings.warn("login_required decorator is deprecated, "
|
||||
"use check_authorized instead",
|
||||
DeprecationWarning)
|
||||
return wrapped
|
||||
return wrapped # type: ignore[return-value]
|
||||
|
||||
|
||||
async def check_permission(request, permission, context=None):
|
||||
async def check_permission(request: web.Request, permission: Union[str, enum.Enum],
|
||||
context: Any = None) -> None:
|
||||
"""Checker that passes only to authoraised users with given permission.
|
||||
|
||||
If user is not authorized - raises HTTPUnauthorized,
|
||||
@@ -134,10 +144,7 @@ async def check_permission(request, permission, context=None):
|
||||
raise web.HTTPForbidden()
|
||||
|
||||
|
||||
def has_permission(
|
||||
permission,
|
||||
context=None,
|
||||
):
|
||||
def has_permission(permission: Union[str, enum.Enum], context: Any = None): # type: ignore
|
||||
"""Decorator that restricts access only for authorized users
|
||||
with correct permissions.
|
||||
|
||||
@@ -145,11 +152,11 @@ def has_permission(
|
||||
if user is authorized and does not have permission -
|
||||
raises HTTPForbidden.
|
||||
"""
|
||||
def wrapper(fn):
|
||||
def wrapper(fn): # type: ignore
|
||||
@wraps(fn)
|
||||
async def wrapped(*args, **kwargs):
|
||||
async def wrapped(*args, **kwargs): # type: ignore
|
||||
request = args[-1]
|
||||
if not isinstance(request, web.BaseRequest):
|
||||
if not isinstance(request, web.Request):
|
||||
msg = ("Incorrect decorator usage. "
|
||||
"Expecting `def handler(request)` "
|
||||
"or `def handler(self, request)`.")
|
||||
@@ -166,7 +173,8 @@ def has_permission(
|
||||
return wrapper
|
||||
|
||||
|
||||
def setup(app, identity_policy, autz_policy):
|
||||
def setup(app: web.Application, identity_policy: AbstractIdentityPolicy,
|
||||
autz_policy: AbstractAuthorizationPolicy) -> None:
|
||||
assert isinstance(identity_policy, AbstractIdentityPolicy), identity_policy
|
||||
assert isinstance(autz_policy, AbstractAuthorizationPolicy), autz_policy
|
||||
|
||||
|
@@ -5,28 +5,32 @@ more handy.
|
||||
|
||||
"""
|
||||
|
||||
from aiohttp import web
|
||||
from typing import Any, NewType, Optional, Union, cast
|
||||
|
||||
from .abc import AbstractIdentityPolicy
|
||||
|
||||
|
||||
sentinel = object()
|
||||
_Sentinel = NewType('_Sentinel', object)
|
||||
sentinel = _Sentinel(object())
|
||||
|
||||
|
||||
class CookiesIdentityPolicy(AbstractIdentityPolicy):
|
||||
|
||||
def __init__(self):
|
||||
def __init__(self) -> None:
|
||||
self._cookie_name = 'AIOHTTP_SECURITY'
|
||||
self._max_age = 30 * 24 * 3600
|
||||
|
||||
async def identify(self, request):
|
||||
identity = request.cookies.get(self._cookie_name)
|
||||
return identity
|
||||
async def identify(self, request: web.Request) -> Optional[str]:
|
||||
return request.cookies.get(self._cookie_name)
|
||||
|
||||
async def remember(self, request, response, identity, max_age=sentinel,
|
||||
**kwargs):
|
||||
async def remember(self, request: web.Request, response: web.StreamResponse,
|
||||
identity: str, max_age: Union[_Sentinel, Optional[int]] = sentinel,
|
||||
**kwargs: Any) -> None:
|
||||
if max_age is sentinel:
|
||||
max_age = self._max_age
|
||||
max_age = cast(Optional[int], max_age)
|
||||
response.set_cookie(self._cookie_name, identity,
|
||||
max_age=max_age, **kwargs)
|
||||
|
||||
async def forget(self, request, response):
|
||||
async def forget(self, request: web.Request, response: web.StreamResponse) -> None:
|
||||
response.del_cookie(self._cookie_name)
|
||||
|
@@ -2,12 +2,17 @@
|
||||
|
||||
"""
|
||||
|
||||
from typing import Optional
|
||||
|
||||
from aiohttp import web
|
||||
|
||||
from .abc import AbstractIdentityPolicy
|
||||
|
||||
try:
|
||||
import jwt
|
||||
HAS_JWT = True
|
||||
except ImportError: # pragma: no cover
|
||||
jwt = None
|
||||
HAS_JWT = False
|
||||
|
||||
|
||||
AUTH_HEADER_NAME = 'Authorization'
|
||||
@@ -15,21 +20,21 @@ AUTH_SCHEME = 'Bearer '
|
||||
|
||||
|
||||
class JWTIdentityPolicy(AbstractIdentityPolicy):
|
||||
def __init__(self, secret, algorithm='HS256'):
|
||||
if jwt is None:
|
||||
def __init__(self, secret: str, algorithm: str = 'HS256'):
|
||||
if not HAS_JWT:
|
||||
raise RuntimeError('Please install `PyJWT`')
|
||||
self.secret = secret
|
||||
self.algorithm = algorithm
|
||||
|
||||
async def identify(self, request):
|
||||
async def identify(self, request: web.Request) -> Optional[str]:
|
||||
header_identity = request.headers.get(AUTH_HEADER_NAME)
|
||||
|
||||
if header_identity is None:
|
||||
return
|
||||
return None
|
||||
|
||||
if not header_identity.startswith(AUTH_SCHEME):
|
||||
raise ValueError('Invalid authorization scheme. ' +
|
||||
'Should be `Bearer <token>`')
|
||||
'Should be `{}<token>`'.format(AUTH_SCHEME))
|
||||
|
||||
token = header_identity.split(' ')[1].strip()
|
||||
|
||||
@@ -38,8 +43,9 @@ class JWTIdentityPolicy(AbstractIdentityPolicy):
|
||||
algorithms=[self.algorithm])
|
||||
return identity
|
||||
|
||||
async def remember(self, *args, **kwargs): # pragma: no cover
|
||||
async def remember(self, request: web.Request, response: web.StreamResponse,
|
||||
identity: str, **kwargs: None) -> None:
|
||||
pass
|
||||
|
||||
async def forget(self, request, response): # pragma: no cover
|
||||
async def forget(self, request: web.Request, response: web.StreamResponse) -> None:
|
||||
pass
|
||||
|
0
aiohttp_security/py.typed
Normal file
0
aiohttp_security/py.typed
Normal file
@@ -4,6 +4,7 @@ aiohttp_session.setup() should be called on application initialization
|
||||
to configure aiohttp_session properly.
|
||||
"""
|
||||
|
||||
from aiohttp import web
|
||||
try:
|
||||
from aiohttp_session import get_session
|
||||
HAS_AIOHTTP_SESSION = True
|
||||
@@ -15,21 +16,22 @@ from .abc import AbstractIdentityPolicy
|
||||
|
||||
class SessionIdentityPolicy(AbstractIdentityPolicy):
|
||||
|
||||
def __init__(self, session_key='AIOHTTP_SECURITY'):
|
||||
def __init__(self, session_key: str = 'AIOHTTP_SECURITY'):
|
||||
self._session_key = session_key
|
||||
|
||||
if not HAS_AIOHTTP_SESSION: # pragma: no cover
|
||||
raise ImportError(
|
||||
'SessionIdentityPolicy requires `aiohttp_session`')
|
||||
|
||||
async def identify(self, request):
|
||||
async def identify(self, request: web.Request) -> str:
|
||||
session = await get_session(request)
|
||||
return session.get(self._session_key)
|
||||
|
||||
async def remember(self, request, response, identity, **kwargs):
|
||||
async def remember(self, request: web.Request, response: web.StreamResponse,
|
||||
identity: str, **kwargs: None) -> None:
|
||||
session = await get_session(request)
|
||||
session[self._session_key] = identity
|
||||
|
||||
async def forget(self, request, response):
|
||||
async def forget(self, request: web.Request, response: web.StreamResponse) -> None:
|
||||
session = await get_session(request)
|
||||
session.pop(self._session_key, None)
|
||||
|
Reference in New Issue
Block a user