from datetime import datetime, timedelta
from enum import Enum
from typing import List, Optional, Tuple, Dict, Any, Union

import time
from authlib.common.security import generate_token
from authlib.consts import default_json_headers
from authlib.oauth2 import (
    OAuth2Request,
    AuthorizationServer as _AuthorizationServer,
    ResourceProtector as _ResourceProtector,
    OAuth2Error,
    HttpRequest,
)
from authlib.oauth2.rfc6749 import InvalidClientError
from authlib.oauth2.rfc6749.grants import (
    AuthorizationCodeGrant as _AuthorizationCodeGrant,
    RefreshTokenGrant as _RefreshTokenGrant,
    BaseGrant,
)
from authlib.oauth2.rfc6749.grants import (
    ResourceOwnerPasswordCredentialsGrant as _ResourceOwnerPasswordCredentialsGrant,
)
from authlib.oauth2.rfc6749.util import scope_to_list
from authlib.oauth2.rfc6750 import BearerTokenValidator as _BearerTokenValidator, BearerToken as _BearerToken, \
    InsufficientScopeError
from authlib.oauth2.rfc8414 import AuthorizationServerMetadata
from authlib.oidc.core import UserInfo
from authlib.oidc.core.grants import (
    OpenIDCode as _OpenIDCode,
    OpenIDImplicitGrant as _OpenIDImplicitGrant,
    OpenIDHybridGrant as _OpenIDHybridGrant,
)
from authlib.oidc.core.grants.util import is_openid_scope, generate_id_token
from fastapi import HTTPException
from starlette.concurrency import run_in_threadpool
from starlette.responses import Response, JSONResponse

from user_manager.common.config import config
from user_manager.common.models import DbAuthorizationCode, DbToken, DbClient, DbUser, DbManagerSchema, DbUserProperty, \
    UserPropertyType
from user_manager.common.mongo import authorization_code_collection, token_collection, \
    client_collection, client_user_cache_collection, user_group_collection, async_token_collection, \
    async_user_group_collection, async_client_collection, user_collection, read_schema, async_read_schema
from . import oauth2_key
from .user_helper import UserWithRoles

USERS_SCOPE = '*users'


class TypedRequest(OAuth2Request):
    user: UserWithRoles
    credential: Union[DbAuthorizationCode, DbToken]
    client: DbClient


class RedirectResponse(Response):
    def to_json_response(self) -> JSONResponse:
        return JSONResponse(
            content={'redirect_uri': self.headers['Location']},
            status_code=200,
            headers=dict(default_json_headers),
        )


class ErrorJSONResponse(JSONResponse):
    pass


class ErrorRedirectResponse(RedirectResponse):
    def to_json_response(self) -> JSONResponse:
        return ErrorJSONResponse(
            content={'redirect_uri': self.headers['Location']},
            status_code=401,
            headers=dict(default_json_headers),
        )


class AuthorizationServer(_AuthorizationServer):
    metadata_class = AuthorizationServerMetadata

    def create_oauth2_request(self, request: TypedRequest):
        assert isinstance(request, OAuth2Request)
        return request

    def create_json_request(self, request):
        assert isinstance(request, HttpRequest)
        raise NotImplementedError()
        # TODO: Create HttpRequest with json in body.

    def handle_response(self, status_code: int, payload: Optional[dict], headers: List[Tuple[str, str]]):
        headers = dict(headers)
        if isinstance(payload, dict):
            return JSONResponse(payload, status_code=status_code, headers=headers)
        elif headers.get('Location'):
            assert not payload
            return RedirectResponse(status_code=status_code, headers=headers)
        assert False

    def handle_error_response(self, request: TypedRequest, error: OAuth2Error):
        status_code, body, headers = error(
            translations=self.get_translations(request),
            error_uris=self.get_error_uris(request)
        )
        headers = dict(headers)
        if isinstance(body, dict):
            return ErrorJSONResponse(
                content=body,
                status_code=status_code,
                headers=headers,
            )
        elif headers.get('Location'):
            assert not body
            return ErrorRedirectResponse(
                status_code=status_code,
                headers=headers,
            )
        assert False


def save_authorization_code(code: str, request: TypedRequest):
    nonce = request.data.get('nonce')
    item = DbAuthorizationCode(
        code=code,
        client_id=request.client.id,
        redirect_uri=request.redirect_uri,
        scope=request.scope,
        user_id=request.user.user.id,
        nonce=nonce,
        auth_time=int(time.time()),
        expiration_time=datetime.utcnow() + timedelta(seconds=config.oauth2.token_expiration.authorization_code),
    )
    authorization_code_collection.insert_one(item.document())
    return item


class ExistsNonceMixin(object):
    def exists_nonce(self, nonce: str, request: TypedRequest):
        # exists = mongo.authorization_code_collection.count_documents(
        #     {'client_id': request.client_id, 'nonce': nonce},
        #     limit=1,
        # )
        mod_result = authorization_code_collection.update_one(
            {'client_id': request.client_id, 'nonce': nonce},
            {'$set': {'nonce': None}},
        )
        if mod_result.modified_count != 1:
            return False
        return True


class JwtConfigMixin(object):
    jwt_token_expiration: int

    def get_jwt_config(self, *args, **kwargs):
        return {
            'key': oauth2_key.key.key,
            'alg': oauth2_key.key.jwk.alg.value,
            'iss': config.oauth2.issuer,
            'exp': self.jwt_token_expiration,
        }


class UserInfoMixin(object):
    def _translate_properties(
            self,
            scope: str,
            schema: DbManagerSchema,
    ) -> List[Tuple[str, DbUserProperty, Optional[str], Optional[bool]]]:
        scope_list = ['*'] + scope_to_list(scope)
        return [
            (prop.valid_key, schema.properties_by_key[prop.user_property], prop.group_type, prop.group_by_name)
            for scope_name in scope_list
            if scope_name not in ('openid', 'offline_access') and scope_name in schema.scopes_by_key
            for prop in schema.scopes_by_key[scope_name].properties
            if prop.user_property in schema.properties_by_key
        ]

    def generate_user_info(self, user: UserWithRoles, scope: str):
        user_data = {
            'roles': user.roles,
        }
        for key, prop, group_type, group_by_name in self._translate_properties(scope, read_schema()):
            if not hasattr(user.user, prop.key):
                continue
            value = getattr(user.user, prop.key, None)
            if prop.type == UserPropertyType.picture:
                if value is not None:
                    value = f"{config.oauth2.base_url}/picture/{value}"
            elif prop.type == UserPropertyType.groups:
                group_filter = {} if group_type is None else {'group_type': group_type}
                value = [
                    group['group_name'] if group_by_name else group['_id']
                    for group in user_group_collection.find(
                        {'_id': {'$in': value}, 'visible': True, **group_filter},
                        projection={'group_name' if group_by_name else '_id': 1}
                    )
                ]
            elif prop.type in (
                    UserPropertyType.access_token, UserPropertyType.password, UserPropertyType.token
            ):
                continue
            user_data[key] = value
        return UserInfo(**user_data)

    async def async_generate_user_info(self, user: UserWithRoles, scope: str):
        user_data = {
            'roles': user.roles,
        }
        for key, prop, group_type, group_by_name in self._translate_properties(scope, await async_read_schema()):
            if not hasattr(user.user, prop.key):
                continue
            value = getattr(user.user, prop.key, None)
            if prop.type == UserPropertyType.picture:
                if value is not None:
                    value = f"{config.oauth2.base_url}/picture/{value}"
            elif prop.type == UserPropertyType.groups:
                group_filter = {} if group_type is None else {'group_type': group_type}
                value = [
                    group['group_name'] if group_by_name else group['_id']
                    async for group in async_user_group_collection.find(
                        {'_id': {'$in': value}, 'visible': True, **group_filter},
                        projection={'group_name' if group_by_name else '_id': 1}
                    )
                ]
            elif prop.type in (
                    UserPropertyType.access_token, UserPropertyType.password, UserPropertyType.token
            ):
                continue
            user_data[key] = value
        return UserInfo(**user_data)


class AuthorizationCodeGrant(_AuthorizationCodeGrant):
    TOKEN_ENDPOINT_AUTH_METHODS = ['none', 'client_secret_basic', 'client_secret_post']
    AUTHORIZATION_CODE_LENGTH = config.oauth2.authorization_code_length

    def save_authorization_code(self, code: str, request: TypedRequest):
        return save_authorization_code(code, request)

    def query_authorization_code(self, code: str, client: DbClient):
        auth_code_data = authorization_code_collection.find_one({'_id': code, 'client_id': client.id})
        if auth_code_data is None:
            return None
        auth_code = DbAuthorizationCode.validate_document(auth_code_data)
        if auth_code.is_expired():
            return None
        return auth_code

    def delete_authorization_code(self, authorization_code: DbAuthorizationCode):
        authorization_code_collection.delete_one({'_id': authorization_code.code})

    def authenticate_user(self, authorization_code: DbAuthorizationCode):
        return UserWithRoles.load(authorization_code.user_id, authorization_code.client_id)


class ResourceOwnerPasswordCredentialsGrant(_ResourceOwnerPasswordCredentialsGrant):

    def authenticate_token_endpoint_client(self):
        # Must override this to set the client in the request, to make it available to authenticate_user
        client = super(self).authenticate_token_endpoint_client()
        self.request.client = client
        return client

    def authenticate_user(self, username: str, password: str):
        user_data = user_collection.find_one({'email': username, 'access_tokens.token': password, 'active': True})
        if user_data is None:
            return None
        return UserWithRoles.load_groups(DbUser.validate_document(user_data), self.client.id)


class OpenIDCode(UserInfoMixin, ExistsNonceMixin, JwtConfigMixin, _OpenIDCode):
    jwt_token_expiration = config.oauth2.token_expiration.authorization_code


class OpenIDImplicitGrant(UserInfoMixin, ExistsNonceMixin, JwtConfigMixin, _OpenIDImplicitGrant):
    jwt_token_expiration = config.oauth2.token_expiration.implicit


class OpenIDHybridGrant(UserInfoMixin, ExistsNonceMixin, JwtConfigMixin, _OpenIDHybridGrant):
    jwt_token_expiration = config.oauth2.token_expiration.implicit

    def generate_authorization_code(self) -> str:
        return generate_token(config.oauth2.authorization_code_length)

    def save_authorization_code(self, code: str, request: TypedRequest):
        return save_authorization_code(code, request)


class RefreshTokenGrant(_RefreshTokenGrant):
    TOKEN_ENDPOINT_AUTH_METHODS = ['none', 'client_secret_basic']
    INCLUDE_NEW_REFRESH_TOKEN = True

    def authenticate_refresh_token(self, refresh_token: str):
        token_data = token_collection.find_one({'refresh_token': refresh_token})
        if token_data is None:
            return None
        auth_code = DbToken.validate_document(token_data)
        if auth_code.is_expired():
            return None
        return auth_code

    def authenticate_user(self, credential: DbToken):
        return UserWithRoles.load(credential.user_id, credential.client_id)

    def revoke_old_credential(self, credential: DbToken):
        # token_collection.update_one({'_id': credential.access_token}, {'revoked': True})
        token_collection.delete_one({'_id': credential.access_token})


def save_token(token: Dict[str, Any], request: TypedRequest):
    if request.user:
        user_id = request.user.user.id
    else:
        user_id = None
    now = int(time.time())
    token_data = DbToken.validate_document({
        'client_id': request.client.id,
        'user_id': user_id,
        'issued_at': now,
        'expiration_time': datetime.utcnow() + timedelta(seconds=token.get('expires_in', 0)),
        'scope': request.scope,
        'auth_time': request.credential.get_auth_time(),
        **token
    })
    token_collection.insert_one(token_data.document())
    return token_data


def query_client(client_id: str):
    client_data = client_collection.find_one({'_id': client_id})
    if client_data is None:
        return None
    return DbClient.validate_document(client_data)


async def async_query_client(client_id: str):
    client_data = await async_client_collection.find_one({'_id': client_id})
    if client_data is None:
        return None
    return DbClient.validate_document(client_data)


def token_generator(*_):
    return generate_token(config.oauth2.token_length)


class AccessTokenGenerator(UserInfoMixin, JwtConfigMixin):
    jwt_token_expiration = config.oauth2.token_expiration.authorization_code

    def __call__(self, client: DbClient, grant_type: str, user: UserWithRoles, scope: str):
        jwt_config = self.get_jwt_config()
        jwt_config['aud'] = [client.get_client_id()]
        jwt_config['auth_time'] = int(time.time())

        user_info = {'sub': user.user.id, 'roles': user.roles}
        if 'groups' in scope_to_list(scope):
            user_info['groups'] = user.user.groups
        return generate_id_token({}, user_info, code=generate_token(config.oauth2.access_token_length), **jwt_config)


def token_expires_in(_, grant_type: str):
    return getattr(config.oauth2.token_expiration, grant_type)


class BearerToken(_BearerToken):
    def __call__(self, client, grant_type, user=None, scope=None,
                 expires_in=None, include_refresh_token=True):
        if 'offline_access' not in scope_to_list(scope):
            include_refresh_token = False
        return super(BearerToken, self).__call__(client, grant_type, user, scope, expires_in, include_refresh_token)


authorization = AuthorizationServer(
    query_client,
    save_token,
    BearerToken(AccessTokenGenerator(), expires_generator=token_expires_in, refresh_token_generator=token_generator),
)


class OpenIDSessionState:

    def __call__(self, grant: BaseGrant):
        grant.register_hook('process_token', self.process_token)

    def process_token(self, grant: BaseGrant, token: dict):
        scope = token.get('scope')
        if not scope or not is_openid_scope(scope):
            # standard authorization code flow
            return token

        token['session_state'] = str(grant.request.user.last_modified)
        return token


# support all openid grants
authorization.register_grant(AuthorizationCodeGrant, [OpenIDCode(), OpenIDSessionState()])
authorization.register_grant(OpenIDImplicitGrant)
authorization.register_grant(OpenIDHybridGrant)
authorization.register_grant(RefreshTokenGrant, [OpenIDCode(), OpenIDSessionState()])
authorization.register_grant(ResourceOwnerPasswordCredentialsGrant)


class BearerTokenValidator(_BearerTokenValidator):
    def authenticate_token(self, token_string: str):
        token_data = token_collection.find_one({'_id': token_string})
        if token_data is None:
            return None
        token = DbToken.validate_document(token_data)
        if client_user_cache_collection.count_documents({
            'client_id': token.client_id,
            'user_id': token.user_id,
        }) != 1:
            return None
        return token

    def request_invalid(self, request: TypedRequest):
        return False

    def token_revoked(self, token: DbToken):
        return token.revoked


class ResourceProtector(_ResourceProtector):
    def validate(self, request: OAuth2Request, scope: str = None, scope_operator='AND') -> DbToken:
        assert isinstance(request, OAuth2Request)
        return self.validate_request(scope, request, scope_operator)


class UserIntrospection(UserInfoMixin):
    async def create_response(self, request: TypedRequest) -> Response:
        try:
            assert isinstance(request, OAuth2Request)
            request.token = await run_in_threadpool(resource_protector.validate_request, None, request)
            if request.token is None:
                raise HTTPException(403, "Invalid token")
            request.user = await UserWithRoles.async_load(request.token.user_id, request.token.client_id)
            user_info = await self.async_generate_user_info(request.user, request.token.scope)
            return JSONResponse(user_info)
        except OAuth2Error as error:
            return authorization.handle_error_response(request, error)


class RequestOriginVerifier:
    async def create_response(self, request: TypedRequest, origin: str) -> Optional[Response]:
        try:
            assert isinstance(request, OAuth2Request)
            request.token = await run_in_threadpool(resource_protector.validate_request, None, request)
            if request.token is None:
                raise HTTPException(403, "Invalid token")
            request.client = await async_query_client(request.token.client_id)
            if request.client is None:
                raise HTTPException(403, "Invalid client in token")
            if not request.client.check_redirect_uri(origin):
                raise HTTPException(403, "Allowed redirect uri does not match request")
            return None
        except OAuth2Error as error:
            return authorization.handle_error_response(request, error)


class OtherUserInspection(UserInfoMixin):
    async def create_response(self, request: TypedRequest, user_id: str, client_auth: dict = None) -> Response:
        try:
            assert isinstance(request, OAuth2Request)
            if request.client is None:
                request.token = await run_in_threadpool(resource_protector.validate_request, None, request)
                if request.token is None:
                    raise HTTPException(403, "Invalid token")
                client_id = request.token.client_id
                scopes = request.token.scope
                scope = USERS_SCOPE
            else:
                client_id = request.client_id
                scopes = request.client.allowed_scope
                scope = scopes
            if USERS_SCOPE not in scope_to_list(scopes):
                raise InsufficientScopeError('Missing "*users" scope', request.uri)
            user = await UserWithRoles.async_load(user_id, client_id)
            if user is None:
                raise HTTPException(404, "User not found")
            user_info = await self.async_generate_user_info(user, scope)
            return JSONResponse(user_info)
        except OAuth2Error as error:
            return authorization.handle_error_response(request, error)


class OtherUsersInspection(UserInfoMixin):
    async def create_response(self, request: TypedRequest) -> Response:
        try:
            assert isinstance(request, OAuth2Request)
            if request.client is None:
                request.token = await run_in_threadpool(resource_protector.validate_request, None, request)
                if request.token is None:
                    raise HTTPException(403, "Invalid token")
                client_id = request.token.client_id
                scopes = request.token.scope
                scope = USERS_SCOPE
                load_roles = False
            else:
                client_id = request.client_id
                scopes = request.client.allowed_scope
                scope = scopes
                load_roles = True
            if USERS_SCOPE not in scope_to_list(scopes):
                raise InsufficientScopeError('Missing "*users" scope', request.uri)
            user_infos = []
            for user in await UserWithRoles.async_load_all(client_id, load_roles=load_roles):
                user_info = await self.async_generate_user_info(user, scope)
                if not load_roles:
                    del user_info['roles']
                user_infos.append(user_info)
            return JSONResponse(user_infos)
        except OAuth2Error as error:
            return authorization.handle_error_response(request, error)


class TypeHint(str, Enum):
    AccessToken = "access_token"
    RefreshToken = "refresh_token"


class RevocationEndpoint:

    async def create_response(
            self, raw_token: str, token_type_hint: Optional[TypeHint], request: TypedRequest
    ) -> Response:
        token_data = None
        if token_type_hint is None or token_type_hint == TypeHint.AccessToken:
            token_data = await async_token_collection.find_one({'_id': raw_token})
        if token_data is None and (token_type_hint is None or token_type_hint == TypeHint.RefreshToken):
            token_data = await async_token_collection.find_one({'refresh_token': raw_token})
        if token_data is None:
            return Response()
        token = DbToken.validate_document(token_data)
        try:
            if request.client_id is None:
                request.data['client_id'] = token.client_id
            elif token.client_id != request.client_id:
                raise InvalidClientError(state=request.state, status_code=401)
            await run_in_threadpool(
                authorization.authenticate_client, request, ["none", "client_secret_basic", "client_secret_post"]
            )
            # await async_token_collection.update_one({'_id': token.access_token}, {'$set': {'revoked': True}})
            # token_collection.update_one({'_id': credential.access_token}, {'revoked': True})
            await async_token_collection.delete_one({'_id': token.access_token})
            return Response()
        except OAuth2Error as error:
            return authorization.handle_error_response(request, error)


resource_protector = ResourceProtector()
resource_protector.register_token_validator(BearerTokenValidator())

user_introspection = UserIntrospection()
token_revocation = RevocationEndpoint()
request_origin_verifier = RequestOriginVerifier()

other_user_inspection = OtherUserInspection()
other_users_inspection = OtherUsersInspection()

[((331, 18, 331, 64), 'user_manager.common.mongo.client_collection.find_one', 'client_collection.find_one', ({(331, 45, 331, 63): "{'_id': client_id}"}, {}), "({'_id': client_id})", False, 'from user_manager.common.mongo import authorization_code_collection, token_collection, client_collection, client_user_cache_collection, user_group_collection, async_token_collection, async_user_group_collection, async_client_collection, user_collection, read_schema, async_read_schema\n'), ((334, 11, 334, 50), 'user_manager.common.models.DbClient.validate_document', 'DbClient.validate_document', ({(334, 38, 334, 49): 'client_data'}, {}), '(client_data)', False, 'from user_manager.common.models import DbAuthorizationCode, DbToken, DbClient, DbUser, DbManagerSchema, DbUserProperty, UserPropertyType\n'), ((341, 11, 341, 50), 'user_manager.common.models.DbClient.validate_document', 'DbClient.validate_document', ({(341, 38, 341, 49): 'client_data'}, {}), '(client_data)', False, 'from user_manager.common.models import DbAuthorizationCode, DbToken, DbClient, DbUser, DbManagerSchema, DbUserProperty, UserPropertyType\n'), ((345, 11, 345, 53), 'authlib.common.security.generate_token', 'generate_token', ({(345, 26, 345, 52): 'config.oauth2.token_length'}, {}), '(config.oauth2.token_length)', False, 'from authlib.common.security import generate_token\n'), ((143, 21, 146, 9), 'user_manager.common.mongo.authorization_code_collection.update_one', 'authorization_code_collection.update_one', ({(144, 12, 144, 60): "{'client_id': request.client_id, 'nonce': nonce}", (145, 12, 145, 37): "{'$set': {'nonce': None}}"}, {}), "({'client_id': request.client_id,\n    'nonce': nonce}, {'$set': {'nonce': None}})", False, 'from user_manager.common.mongo import authorization_code_collection, token_collection, client_collection, client_user_cache_collection, user_group_collection, async_token_collection, async_user_group_collection, async_client_collection, user_collection, read_schema, async_read_schema\n'), ((204, 15, 204, 36), 'authlib.oidc.core.UserInfo', 'UserInfo', ({}, {}), '(**user_data)', False, 'from authlib.oidc.core import UserInfo\n'), ((231, 15, 231, 36), 'authlib.oidc.core.UserInfo', 'UserInfo', ({}, {}), '(**user_data)', False, 'from authlib.oidc.core import UserInfo\n'), ((242, 25, 242, 102), 'user_manager.common.mongo.authorization_code_collection.find_one', 'authorization_code_collection.find_one', ({(242, 64, 242, 101): "{'_id': code, 'client_id': client.id}"}, {}), "({'_id': code, 'client_id': client.id})", False, 'from user_manager.common.mongo import authorization_code_collection, token_collection, client_collection, client_user_cache_collection, user_group_collection, async_token_collection, async_user_group_collection, async_client_collection, user_collection, read_schema, async_read_schema\n'), ((245, 20, 245, 73), 'user_manager.common.models.DbAuthorizationCode.validate_document', 'DbAuthorizationCode.validate_document', ({(245, 58, 245, 72): 'auth_code_data'}, {}), '(auth_code_data)', False, 'from user_manager.common.models import DbAuthorizationCode, DbToken, DbClient, DbUser, DbManagerSchema, DbUserProperty, UserPropertyType\n'), ((251, 8, 251, 82), 'user_manager.common.mongo.authorization_code_collection.delete_one', 'authorization_code_collection.delete_one', ({(251, 49, 251, 81): "{'_id': authorization_code.code}"}, {}), "({'_id': authorization_code.code})", False, 'from user_manager.common.mongo import authorization_code_collection, token_collection, client_collection, client_user_cache_collection, user_group_collection, async_token_collection, async_user_group_collection, async_client_collection, user_collection, read_schema, async_read_schema\n'), ((266, 20, 266, 114), 'user_manager.common.mongo.user_collection.find_one', 'user_collection.find_one', ({(266, 45, 266, 113): "{'email': username, 'access_tokens.token': password, 'active': True}"}, {}), "({'email': username, 'access_tokens.token':\n    password, 'active': True})", False, 'from user_manager.common.mongo import authorization_code_collection, token_collection, client_collection, client_user_cache_collection, user_group_collection, async_token_collection, async_user_group_collection, async_client_collection, user_collection, read_schema, async_read_schema\n'), ((284, 15, 284, 70), 'authlib.common.security.generate_token', 'generate_token', ({(284, 30, 284, 69): 'config.oauth2.authorization_code_length'}, {}), '(config.oauth2.authorization_code_length)', False, 'from authlib.common.security import generate_token\n'), ((295, 21, 295, 80), 'user_manager.common.mongo.token_collection.find_one', 'token_collection.find_one', ({(295, 47, 295, 79): "{'refresh_token': refresh_token}"}, {}), "({'refresh_token': refresh_token})", False, 'from user_manager.common.mongo import authorization_code_collection, token_collection, client_collection, client_user_cache_collection, user_group_collection, async_token_collection, async_user_group_collection, async_client_collection, user_collection, read_schema, async_read_schema\n'), ((298, 20, 298, 57), 'user_manager.common.models.DbToken.validate_document', 'DbToken.validate_document', ({(298, 46, 298, 56): 'token_data'}, {}), '(token_data)', False, 'from user_manager.common.models import DbAuthorizationCode, DbToken, DbClient, DbUser, DbManagerSchema, DbUserProperty, UserPropertyType\n'), ((308, 8, 308, 69), 'user_manager.common.mongo.token_collection.delete_one', 'token_collection.delete_one', ({(308, 36, 308, 68): "{'_id': credential.access_token}"}, {}), "({'_id': credential.access_token})", False, 'from user_manager.common.mongo import authorization_code_collection, token_collection, client_collection, client_user_cache_collection, user_group_collection, async_token_collection, async_user_group_collection, async_client_collection, user_collection, read_schema, async_read_schema\n'), ((316, 14, 316, 25), 'time.time', 'time.time', ({}, {}), '()', False, 'import time\n'), ((338, 24, 338, 76), 'user_manager.common.mongo.async_client_collection.find_one', 'async_client_collection.find_one', ({(338, 57, 338, 75): "{'_id': client_id}"}, {}), "({'_id': client_id})", False, 'from user_manager.common.mongo import authorization_code_collection, token_collection, client_collection, client_user_cache_collection, user_group_collection, async_token_collection, async_user_group_collection, async_client_collection, user_collection, read_schema, async_read_schema\n'), ((406, 21, 406, 69), 'user_manager.common.mongo.token_collection.find_one', 'token_collection.find_one', ({(406, 47, 406, 68): "{'_id': token_string}"}, {}), "({'_id': token_string})", False, 'from user_manager.common.mongo import authorization_code_collection, token_collection, client_collection, client_user_cache_collection, user_group_collection, async_token_collection, async_user_group_collection, async_client_collection, user_collection, read_schema, async_read_schema\n'), ((409, 16, 409, 53), 'user_manager.common.models.DbToken.validate_document', 'DbToken.validate_document', ({(409, 42, 409, 52): 'token_data'}, {}), '(token_data)', False, 'from user_manager.common.models import DbAuthorizationCode, DbToken, DbClient, DbUser, DbManagerSchema, DbUserProperty, UserPropertyType\n'), ((534, 16, 534, 53), 'user_manager.common.models.DbToken.validate_document', 'DbToken.validate_document', ({(534, 42, 534, 52): 'token_data'}, {}), '(token_data)', False, 'from user_manager.common.models import DbAuthorizationCode, DbToken, DbClient, DbUser, DbManagerSchema, DbUserProperty, UserPropertyType\n'), ((94, 19, 94, 82), 'starlette.responses.JSONResponse', 'JSONResponse', (), '', False, 'from starlette.responses import Response, JSONResponse\n'), ((170, 29, 170, 49), 'authlib.oauth2.rfc6749.util.scope_to_list', 'scope_to_list', ({(170, 43, 170, 48): 'scope'}, {}), '(scope)', False, 'from authlib.oauth2.rfc6749.util import scope_to_list\n'), ((183, 86, 183, 99), 'user_manager.common.mongo.read_schema', 'read_schema', ({}, {}), '()', False, 'from user_manager.common.mongo import authorization_code_collection, token_collection, client_collection, client_user_cache_collection, user_group_collection, async_token_collection, async_user_group_collection, async_client_collection, user_collection, read_schema, async_read_schema\n'), ((269, 41, 269, 76), 'user_manager.common.models.DbUser.validate_document', 'DbUser.validate_document', ({(269, 66, 269, 75): 'user_data'}, {}), '(user_data)', False, 'from user_manager.common.models import DbAuthorizationCode, DbToken, DbClient, DbUser, DbManagerSchema, DbUserProperty, UserPropertyType\n'), ((354, 38, 354, 49), 'time.time', 'time.time', ({}, {}), '()', False, 'import time\n'), ((357, 23, 357, 43), 'authlib.oauth2.rfc6749.util.scope_to_list', 'scope_to_list', ({(357, 37, 357, 42): 'scope'}, {}), '(scope)', False, 'from authlib.oauth2.rfc6749.util import scope_to_list\n'), ((369, 35, 369, 55), 'authlib.oauth2.rfc6749.util.scope_to_list', 'scope_to_list', ({(369, 49, 369, 54): 'scope'}, {}), '(scope)', False, 'from authlib.oauth2.rfc6749.util import scope_to_list\n'), ((410, 11, 413, 10), 'user_manager.common.mongo.client_user_cache_collection.count_documents', 'client_user_cache_collection.count_documents', ({(410, 56, 413, 9): "{'client_id': token.client_id, 'user_id': token.user_id}"}, {}), "({'client_id': token.client_id,\n    'user_id': token.user_id})", False, 'from user_manager.common.mongo import authorization_code_collection, token_collection, client_collection, client_user_cache_collection, user_group_collection, async_token_collection, async_user_group_collection, async_client_collection, user_collection, read_schema, async_read_schema\n'), ((439, 19, 439, 42), 'starlette.responses.JSONResponse', 'JSONResponse', ({(439, 32, 439, 41): 'user_info'}, {}), '(user_info)', False, 'from starlette.responses import Response, JSONResponse\n'), ((482, 19, 482, 42), 'starlette.responses.JSONResponse', 'JSONResponse', ({(482, 32, 482, 41): 'user_info'}, {}), '(user_info)', False, 'from starlette.responses import Response, JSONResponse\n'), ((512, 19, 512, 43), 'starlette.responses.JSONResponse', 'JSONResponse', ({(512, 32, 512, 42): 'user_infos'}, {}), '(user_infos)', False, 'from starlette.responses import Response, JSONResponse\n'), ((533, 19, 533, 29), 'starlette.responses.Response', 'Response', ({}, {}), '()', False, 'from starlette.responses import Response, JSONResponse\n'), ((546, 19, 546, 29), 'starlette.responses.Response', 'Response', ({}, {}), '()', False, 'from starlette.responses import Response, JSONResponse\n'), ((130, 22, 130, 33), 'time.time', 'time.time', ({}, {}), '()', False, 'import time\n'), ((131, 24, 131, 41), 'datetime.datetime.utcnow', 'datetime.utcnow', ({}, {}), '()', False, 'from datetime import datetime, timedelta\n'), ((131, 44, 131, 112), 'datetime.timedelta', 'timedelta', (), '', False, 'from datetime import datetime, timedelta\n'), ((210, 92, 210, 111), 'user_manager.common.mongo.async_read_schema', 'async_read_schema', ({}, {}), '()', False, 'from user_manager.common.mongo import authorization_code_collection, token_collection, client_collection, client_user_cache_collection, user_group_collection, async_token_collection, async_user_group_collection, async_client_collection, user_collection, read_schema, async_read_schema\n'), ((321, 27, 321, 44), 'datetime.datetime.utcnow', 'datetime.utcnow', ({}, {}), '()', False, 'from datetime import datetime, timedelta\n'), ((359, 53, 359, 102), 'authlib.common.security.generate_token', 'generate_token', ({(359, 68, 359, 101): 'config.oauth2.access_token_length'}, {}), '(config.oauth2.access_token_length)', False, 'from authlib.common.security import generate_token\n'), ((388, 28, 388, 50), 'authlib.oidc.core.grants.util.is_openid_scope', 'is_openid_scope', ({(388, 44, 388, 49): 'scope'}, {}), '(scope)', False, 'from authlib.oidc.core.grants.util import is_openid_scope, generate_id_token\n'), ((434, 34, 434, 103), 'starlette.concurrency.run_in_threadpool', 'run_in_threadpool', ({(434, 52, 434, 87): 'resource_protector.validate_request', (434, 89, 434, 93): 'None', (434, 95, 434, 102): 'request'}, {}), '(resource_protector.validate_request, None, request)', False, 'from starlette.concurrency import run_in_threadpool\n'), ((436, 22, 436, 57), 'fastapi.HTTPException', 'HTTPException', ({(436, 36, 436, 39): '(403)', (436, 41, 436, 56): '"""Invalid token"""'}, {}), "(403, 'Invalid token')", False, 'from fastapi import HTTPException\n'), ((448, 34, 448, 103), 'starlette.concurrency.run_in_threadpool', 'run_in_threadpool', ({(448, 52, 448, 87): 'resource_protector.validate_request', (448, 89, 448, 93): 'None', (448, 95, 448, 102): 'request'}, {}), '(resource_protector.validate_request, None, request)', False, 'from starlette.concurrency import run_in_threadpool\n'), ((450, 22, 450, 57), 'fastapi.HTTPException', 'HTTPException', ({(450, 36, 450, 39): '(403)', (450, 41, 450, 56): '"""Invalid token"""'}, {}), "(403, 'Invalid token')", False, 'from fastapi import HTTPException\n'), ((453, 22, 453, 67), 'fastapi.HTTPException', 'HTTPException', ({(453, 36, 453, 39): '(403)', (453, 41, 453, 66): '"""Invalid client in token"""'}, {}), "(403, 'Invalid client in token')", False, 'from fastapi import HTTPException\n'), ((455, 22, 455, 87), 'fastapi.HTTPException', 'HTTPException', ({(455, 36, 455, 39): '(403)', (455, 41, 455, 86): '"""Allowed redirect uri does not match request"""'}, {}), "(403, 'Allowed redirect uri does not match request')", False, 'from fastapi import HTTPException\n'), ((476, 34, 476, 55), 'authlib.oauth2.rfc6749.util.scope_to_list', 'scope_to_list', ({(476, 48, 476, 54): 'scopes'}, {}), '(scopes)', False, 'from authlib.oauth2.rfc6749.util import scope_to_list\n'), ((477, 22, 477, 83), 'authlib.oauth2.rfc6750.InsufficientScopeError', 'InsufficientScopeError', ({(477, 45, 477, 69): '"""Missing "*users" scope"""', (477, 71, 477, 82): 'request.uri'}, {}), '(\'Missing "*users" scope\', request.uri)', False, 'from authlib.oauth2.rfc6750 import BearerTokenValidator as _BearerTokenValidator, BearerToken as _BearerToken, InsufficientScopeError\n'), ((480, 22, 480, 58), 'fastapi.HTTPException', 'HTTPException', ({(480, 36, 480, 39): '(404)', (480, 41, 480, 57): '"""User not found"""'}, {}), "(404, 'User not found')", False, 'from fastapi import HTTPException\n'), ((504, 34, 504, 55), 'authlib.oauth2.rfc6749.util.scope_to_list', 'scope_to_list', ({(504, 48, 504, 54): 'scopes'}, {}), '(scopes)', False, 'from authlib.oauth2.rfc6749.util import scope_to_list\n'), ((505, 22, 505, 83), 'authlib.oauth2.rfc6750.InsufficientScopeError', 'InsufficientScopeError', ({(505, 45, 505, 69): '"""Missing "*users" scope"""', (505, 71, 505, 82): 'request.uri'}, {}), '(\'Missing "*users" scope\', request.uri)', False, 'from authlib.oauth2.rfc6750 import BearerTokenValidator as _BearerTokenValidator, BearerToken as _BearerToken, InsufficientScopeError\n'), ((529, 31, 529, 82), 'user_manager.common.mongo.async_token_collection.find_one', 'async_token_collection.find_one', ({(529, 63, 529, 81): "{'_id': raw_token}"}, {}), "({'_id': raw_token})", False, 'from user_manager.common.mongo import authorization_code_collection, token_collection, client_collection, client_user_cache_collection, user_group_collection, async_token_collection, async_user_group_collection, async_client_collection, user_collection, read_schema, async_read_schema\n'), ((531, 31, 531, 92), 'user_manager.common.mongo.async_token_collection.find_one', 'async_token_collection.find_one', ({(531, 63, 531, 91): "{'refresh_token': raw_token}"}, {}), "({'refresh_token': raw_token})", False, 'from user_manager.common.mongo import authorization_code_collection, token_collection, client_collection, client_user_cache_collection, user_group_collection, async_token_collection, async_user_group_collection, async_client_collection, user_collection, read_schema, async_read_schema\n'), ((540, 18, 542, 13), 'starlette.concurrency.run_in_threadpool', 'run_in_threadpool', ({(541, 16, 541, 49): 'authorization.authenticate_client', (541, 51, 541, 58): 'request', (541, 60, 541, 113): "['none', 'client_secret_basic', 'client_secret_post']"}, {}), "(authorization.authenticate_client, request, ['none',\n    'client_secret_basic', 'client_secret_post'])", False, 'from starlette.concurrency import run_in_threadpool\n'), ((545, 18, 545, 80), 'user_manager.common.mongo.async_token_collection.delete_one', 'async_token_collection.delete_one', ({(545, 52, 545, 79): "{'_id': token.access_token}"}, {}), "({'_id': token.access_token})", False, 'from user_manager.common.mongo import authorization_code_collection, token_collection, client_collection, client_user_cache_collection, user_group_collection, async_token_collection, async_user_group_collection, async_client_collection, user_collection, read_schema, async_read_schema\n'), ((466, 38, 466, 107), 'starlette.concurrency.run_in_threadpool', 'run_in_threadpool', ({(466, 56, 466, 91): 'resource_protector.validate_request', (466, 93, 466, 97): 'None', (466, 99, 466, 106): 'request'}, {}), '(resource_protector.validate_request, None, request)', False, 'from starlette.concurrency import run_in_threadpool\n'), ((468, 26, 468, 61), 'fastapi.HTTPException', 'HTTPException', ({(468, 40, 468, 43): '(403)', (468, 45, 468, 60): '"""Invalid token"""'}, {}), "(403, 'Invalid token')", False, 'from fastapi import HTTPException\n'), ((492, 38, 492, 107), 'starlette.concurrency.run_in_threadpool', 'run_in_threadpool', ({(492, 56, 492, 91): 'resource_protector.validate_request', (492, 93, 492, 97): 'None', (492, 99, 492, 106): 'request'}, {}), '(resource_protector.validate_request, None, request)', False, 'from starlette.concurrency import run_in_threadpool\n'), ((494, 26, 494, 61), 'fastapi.HTTPException', 'HTTPException', ({(494, 40, 494, 43): '(403)', (494, 45, 494, 60): '"""Invalid token"""'}, {}), "(403, 'Invalid token')", False, 'from fastapi import HTTPException\n'), ((539, 22, 539, 78), 'authlib.oauth2.rfc6749.InvalidClientError', 'InvalidClientError', (), '', False, 'from authlib.oauth2.rfc6749 import InvalidClientError\n'), ((194, 33, 197, 21), 'user_manager.common.mongo.user_group_collection.find', 'user_group_collection.find', (), '', False, 'from user_manager.common.mongo import authorization_code_collection, token_collection, client_collection, client_user_cache_collection, user_group_collection, async_token_collection, async_user_group_collection, async_client_collection, user_collection, read_schema, async_read_schema\n'), ((221, 39, 224, 21), 'user_manager.common.mongo.async_user_group_collection.find', 'async_user_group_collection.find', (), '', False, 'from user_manager.common.mongo import authorization_code_collection, token_collection, client_collection, client_user_cache_collection, user_group_collection, async_token_collection, async_user_group_collection, async_client_collection, user_collection, read_schema, async_read_schema\n')]