Source code for sni.api.routers.token

"""
Token management paths
"""

from datetime import datetime
import logging
from typing import List, Optional

from fastapi import (
    APIRouter,
    Depends,
    HTTPException,
    status,
)
import pydantic as pdt

from sni.esi.scope import EsiScope
from sni.esi.sso import get_auth_url
from sni.uac.clearance import assert_has_clearance
from sni.uac.token import (
    create_dynamic_app_token,
    create_permanent_app_token,
    create_state_code,
    create_user_token,
    from_authotization_header_nondyn,
    from_authotization_header,
    to_jwt,
    Token,
)
from sni.user.models import User

from .user import GetUserOut

router = APIRouter()


[docs]class GetTokenOut(pdt.BaseModel): """ Model for ``GET /token`` responses. """ callback: Optional[pdt.AnyHttpUrl] comments: Optional[str] created_on: datetime expires_on: Optional[datetime] owner: GetUserOut parent: Optional[str] token_type: Token.TokenType uuid: str
[docs] @staticmethod def from_record(tkn: Token) -> "GetTokenOut": """ Converts a :class:`sni.uac.models.Token` to a :class:`sni.api.routers.token.GetTokenOut` """ return GetTokenOut( callback=tkn.callback, comments=tkn.comments, created_on=tkn.created_on, expires_on=tkn.expires_on, owner=GetUserOut.from_record(tkn.owner), parent=str(tkn.parent.uuid) if tkn.parent is not None else None, token_type=tkn.token_type, uuid=str(tkn.uuid), )
[docs]class PostTokenUseFromDynIn(pdt.BaseModel): """ Model for ``POST /token/use/from/dyn`` requests. """ scopes: List[EsiScope] = pdt.Field([EsiScope.PUBLICDATA]) state_code: Optional[str] = None
[docs]class PostTokenUseFromDynOut(pdt.BaseModel): """ Model for ``POST /token/use/from/dyn`` reponses. """ login_url: str state_code: str
[docs]class PostUseFromPerOut(pdt.BaseModel): """ Model for ``POST /token/use/from/per`` reponses. """ user_token: str
[docs]class PostTokenDynIn(pdt.BaseModel): """ Model for ``POST /token/dyn`` requests. """ callback: pdt.AnyHttpUrl comments: Optional[str]
[docs]class PostTokenDynOut(pdt.BaseModel): """ Model for ``POST /token/dyn`` reponses. """ tkn: str
[docs]class PostTokenPerIn(pdt.BaseModel): """ Model for ``POST /token/per`` requests. """ callback: pdt.AnyHttpUrl comments: Optional[str]
[docs]class PostTokenPerOut(pdt.BaseModel): """ Model for ``POST /token/per`` reponses. """ tkn: str
[docs]@router.delete( "", summary="Manually delete a token", ) async def delete_token( uuid: pdt.UUID4, tkn: Token = Depends(from_authotization_header_nondyn), ): """ Manually deletes a token (of any type). Requires a clearance level of 10. """ tok: Token = Token.objects.get(uuid=uuid) if tok.token_type == Token.TokenType.dyn: assert_has_clearance(tkn.owner, "sni.delete_dyn_token") elif tok.token_type == Token.TokenType.per: assert_has_clearance(tkn.owner, "sni.delete_per_token") elif tok.token_type == Token.TokenType.use: assert_has_clearance(tkn.owner, "sni.delete_use_token") else: logging.error("Token %s has unknown type %s", tok.uuid, tok.token_type) raise PermissionError tok.delete() logging.debug("Deleted token %s", uuid)
[docs]@router.get( "", response_model=GetTokenOut, summary="Get basic informations about the current token", ) async def get_token(tkn: Token = Depends(from_authotization_header_nondyn)): """ Returns informations about the token currently being used. Requires a clearance level of 0 or more. """ assert_has_clearance(tkn.owner, "sni.read_own_token") return GetTokenOut.from_record(tkn)
[docs]@router.post( "/dyn", response_model=PostTokenDynOut, summary="Create a new dynamic app token", ) async def post_token_dyn( data: PostTokenDynIn, tkn: Token = Depends(from_authotization_header_nondyn), ): """ Creates a new dynamic app token. Must be called with a permanent app token, and the owner must have a clearance level of 10. """ assert_has_clearance(tkn.owner, "sni.create_dyn_token") if tkn.token_type != Token.TokenType.per: raise HTTPException(status.HTTP_401_UNAUTHORIZED) new_token = create_dynamic_app_token( tkn.owner, callback=data.callback, comments=data.comments, parent=tkn, ) return PostTokenDynOut(tkn=to_jwt(new_token))
[docs]@router.post( "/per", response_model=PostTokenPerOut, summary="Create a new permanent app token", ) async def post_token_per( data: PostTokenPerIn, tkn: Token = Depends(from_authotization_header_nondyn), ): """ Creates a new permanent app token. Must be called with a permanent app token, and the owner must have a clearance level of 10. """ assert_has_clearance(tkn.owner, "sni.create_per_token") if tkn.token_type != Token.TokenType.per: raise HTTPException(status.HTTP_401_UNAUTHORIZED) new_token = create_permanent_app_token( tkn.owner, callback=data.callback, comments=data.comments, parent=tkn, ) return PostTokenPerOut(tkn=to_jwt(new_token))
[docs]@router.post( "/use/from/dyn", response_model=PostTokenUseFromDynOut, summary="Create a new user token with a dynamic app token", ) async def post_token_use_from_dyn( data: PostTokenUseFromDynIn, tkn: Token = Depends(from_authotization_header), ): """ Authenticates an application dynamic token and returns a `state code` and an URL at which the user can authenticate to the EVE SSO. Once that is done, SNI issues a GET request to the app predefined callback, with that state code and the user token. Requires the owner of the token to have a clearance level of 0 or more. """ if tkn.token_type != Token.TokenType.dyn: raise HTTPException( status.HTTP_401_UNAUTHORIZED, detail="Must use a dynamic app token for this path.", ) assert_has_clearance(tkn.owner, "sni.create_use_token") state_code = ( data.state_code if data.state_code is not None else str(create_state_code(tkn).uuid) ) return PostTokenUseFromDynOut( login_url=get_auth_url(data.scopes, state_code), state_code=state_code, )
[docs]@router.post( "/use/from/per", response_model=PostUseFromPerOut, summary="Create a new user token with a permanent app token", ) async def post_token_use_from_per( tkn: Token = Depends(from_authotization_header), ): """ Authenticates an application permanent token and returns a user token tied to the owner of that app token. """ if tkn.token_type != Token.TokenType.per: raise HTTPException( status.HTTP_401_UNAUTHORIZED, detail="Must use a permanent app token for this path.", ) assert_has_clearance(tkn.owner, "sni.create_use_token") user_token = create_user_token(tkn, User.objects.get(character_id=0),) user_token_str = to_jwt(user_token) return PostUseFromPerOut(user_token=user_token_str)