API - init resource protector (that also handles current authentication)
This commit is contained in:
@ -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())
|
||||
|
80
fittrackee/oauth2/resource_protector.py
Normal file
80
fittrackee/oauth2/resource_protector.py
Normal 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
|
@ -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:
|
||||
|
@ -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()
|
||||
|
Reference in New Issue
Block a user