API - init resource protector (that also handles current authentication)

This commit is contained in:
Sam
2022-05-27 15:51:40 +02:00
parent eeae632b01
commit 44c16f6805
15 changed files with 145 additions and 121 deletions

View File

@ -1,11 +1,14 @@
from authlib.integrations.sqla_oauth2 import create_revocation_endpoint
from authlib.integrations.sqla_oauth2 import (
create_bearer_token_validator,
create_revocation_endpoint,
)
from authlib.oauth2.rfc7636 import CodeChallenge
from flask import Flask
from fittrackee import db
from .grants import AuthorizationCodeGrant, OAuth2Token, RefreshTokenGrant
from .server import authorization_server
from .server import authorization_server, require_auth
def config_oauth(app: Flask) -> None:
@ -21,3 +24,7 @@ def config_oauth(app: Flask) -> None:
revocation_cls = create_revocation_endpoint(db.session, OAuth2Token)
revocation_cls.CLIENT_AUTH_METHODS = ['client_secret_post']
authorization_server.register_endpoint(revocation_cls)
# protect resource
bearer_cls = create_bearer_token_validator(db.session, OAuth2Token)
require_auth.register_token_validator(bearer_cls())

View File

@ -0,0 +1,80 @@
from functools import wraps
from typing import Any, Callable, List, Union
from authlib.integrations.flask_oauth2 import ResourceProtector
from authlib.oauth2 import OAuth2Error
from authlib.oauth2.rfc6749.errors import MissingAuthorizationError
from flask import current_app, request
from werkzeug.exceptions import RequestEntityTooLarge
from fittrackee.responses import (
ForbiddenErrorResponse,
PayloadTooLargeErrorResponse,
UnauthorizedErrorResponse,
)
from fittrackee.users.models import User
class CustomResourceProtector(ResourceProtector):
def __call__(
self,
scopes: Union[str, List] = None,
as_admin: bool = False,
) -> Callable:
def wrapper(f: Callable) -> Callable:
@wraps(f)
def decorated(*args: Any, **kwargs: Any) -> Callable:
auth_user = None
auth_header = request.headers.get('Authorization')
if not auth_header:
return UnauthorizedErrorResponse(
'provide a valid auth token'
)
# First-party application (Fittrackee front-end)
# in this case, scopes will be ignored
auth_token = auth_header.split(' ')[1]
resp = User.decode_auth_token(auth_token)
if isinstance(resp, int):
auth_user = User.query.filter_by(id=resp).first()
# Third-party applications
if not auth_user:
current_token = None
try:
current_token = self.acquire_token(scopes)
except MissingAuthorizationError as error:
self.raise_error_response(error)
except OAuth2Error as error:
self.raise_error_response(error)
except RequestEntityTooLarge:
file_type = ''
if request.endpoint in [
'auth.edit_picture',
'workouts.post_workout',
]:
file_type = (
'picture'
if request.endpoint == 'auth.edit_picture'
else 'workout'
)
return PayloadTooLargeErrorResponse(
file_type=file_type,
file_size=request.content_length,
max_size=current_app.config['MAX_CONTENT_LENGTH'],
)
auth_user = (
None if current_token is None else current_token.user
)
if not auth_user or not auth_user.is_active:
return UnauthorizedErrorResponse(
'provide a valid auth token'
)
if as_admin and not auth_user.admin:
return ForbiddenErrorResponse()
return f(auth_user, *args, **kwargs)
return decorated
return wrapper

View File

@ -3,8 +3,8 @@ from typing import Dict, Tuple, Union
from flask import Blueprint, Response, request
from fittrackee import db
from fittrackee.oauth2.server import require_auth
from fittrackee.responses import HttpResponse, InvalidPayloadErrorResponse
from fittrackee.users.decorators import authenticate
from fittrackee.users.models import User
from .client import create_oauth_client
@ -21,7 +21,7 @@ EXPECTED_METADATA_KEYS = [
@oauth_blueprint.route('/oauth/apps', methods=['POST'])
@authenticate
@require_auth()
def create_client(auth_user: User) -> Union[HttpResponse, Tuple[Dict, int]]:
client_metadata = request.get_json()
if not client_metadata:
@ -55,7 +55,7 @@ def create_client(auth_user: User) -> Union[HttpResponse, Tuple[Dict, int]]:
@oauth_blueprint.route('/oauth/authorize', methods=['POST'])
@authenticate
@require_auth()
def authorize(auth_user: User) -> Response:
data = request.form
if not data or 'client_id' not in data or 'response_type' not in data:

View File

@ -7,6 +7,7 @@ from authlib.integrations.sqla_oauth2 import (
from fittrackee import db
from .models import OAuth2Client, OAuth2Token
from .resource_protector import CustomResourceProtector
query_client = create_query_client_func(db.session, OAuth2Client)
save_token = create_save_token_func(db.session, OAuth2Token)
@ -14,3 +15,4 @@ authorization_server = AuthorizationServer(
query_client=query_client,
save_token=save_token,
)
require_auth = CustomResourceProtector()