Browse Source

Refactor: Moved token validation, made token list a page, documentation

merge-requests/1/head
Drew Short 5 years ago
parent
commit
1450fa596d
  1. 40
      server/corvus/api/authentication_api.py
  2. 2
      server/corvus/middleware/authentication_middleware.py
  3. 21
      server/corvus/service/authentication_service.py
  4. 34
      server/corvus/service/user_token_service.py

40
server/corvus/api/authentication_api.py

@ -2,7 +2,7 @@
from flask import Blueprint, g, abort, request from flask import Blueprint, g, abort, request
from corvus.api.decorators import return_json from corvus.api.decorators import return_json
from corvus.api.model import APIMessage, APIResponse
from corvus.api.model import APIMessage, APIResponse, APIPage
from corvus.middleware import authentication_middleware from corvus.middleware import authentication_middleware
from corvus.service import ( from corvus.service import (
user_token_service, user_token_service,
@ -13,6 +13,7 @@ from corvus.service import (
from corvus.middleware.authentication_middleware import Auth from corvus.middleware.authentication_middleware import Auth
from corvus.service.role_service import Role from corvus.service.role_service import Role
from corvus.model import UserToken from corvus.model import UserToken
from corvus.utility.pagination_utility import get_pagination_params
AUTH_BLUEPRINT = Blueprint( AUTH_BLUEPRINT = Blueprint(
name='auth', import_name=__name__, url_prefix='/auth') name='auth', import_name=__name__, url_prefix='/auth')
@ -61,17 +62,34 @@ def logout() -> APIResponse:
@return_json @return_json
@authentication_middleware.require(required_auth=Auth.BASIC, required_role=Role.USER) @authentication_middleware.require(required_auth=Auth.BASIC, required_role=Role.USER)
def get_tokens() -> APIResponse: def get_tokens() -> APIResponse:
user_tokens = user_token_service.find_by_user(g.user)
return APIResponse(user_tokens, 200)
"""
Get a list of all tokens for the current user
:return: a paginated list of user tokens
"""
page, per_page = get_pagination_params(request.args)
user_token_page = user_token_service.find_by_user(g.user, page, per_page)
if user_token_page is not None:
return APIResponse(APIPage.from_page(user_token_page), 200)
return abort(404)
@AUTH_BLUEPRINT.route('/token', methods=['POST']) @AUTH_BLUEPRINT.route('/token', methods=['POST'])
@return_json @return_json
@authentication_middleware.require(required_auth=Auth.BASIC, required_role=Role.USER) @authentication_middleware.require(required_auth=Auth.BASIC, required_role=Role.USER)
def create_token(): def create_token():
"""
Create a new token with optional parameters
note: String
enabled: Boolean
expirationTime: DateTime
:return: The new token with the optional parameters
"""
requested_token: UserToken = transformation_service.deserialize_model( requested_token: UserToken = transformation_service.deserialize_model(
UserToken, request.json, options=['note', 'enabled', 'expirationTime']) UserToken, request.json, options=['note', 'enabled', 'expirationTime'])
user_token = user_token_service.create(g.user, requested_token.note, requested_token.enabled, requested_token.expiration_time)
user_token = user_token_service.create(
g.user, requested_token.note, requested_token.enabled, requested_token.expiration_time)
return APIResponse(user_token, 200) return APIResponse(user_token, 200)
@ -79,6 +97,12 @@ def create_token():
@return_json @return_json
@authentication_middleware.require(required_auth=Auth.BASIC, required_role=Role.USER) @authentication_middleware.require(required_auth=Auth.BASIC, required_role=Role.USER)
def get_token(token: str): def get_token(token: str):
"""
Retrieve a specific token for this user
:param token: The token to retrieve for this user
:return: The token if it exists
"""
user_token = user_token_service.find_by_user_and_token(g.user, token) user_token = user_token_service.find_by_user_and_token(g.user, token)
if user_token is None: if user_token is None:
return abort(404) return abort(404)
@ -89,8 +113,14 @@ def get_token(token: str):
@return_json @return_json
@authentication_middleware.require(required_auth=Auth.BASIC, required_role=Role.USER) @authentication_middleware.require(required_auth=Auth.BASIC, required_role=Role.USER)
def delete_token(token: str): def delete_token(token: str):
"""
Delete a specific token for this user
:param token: The token to delete for this user
:return: Nothing on success
"""
user_token = user_token_service.find_by_user_and_token(g.user, token) user_token = user_token_service.find_by_user_and_token(g.user, token)
if user_token is None: if user_token is None:
return abort(404) return abort(404)
user_token_service.delete(user_token) user_token_service.delete(user_token)
return APIResponse(None, 200)
return APIResponse(APIMessage(True, None), 200)

2
server/corvus/middleware/authentication_middleware.py

@ -54,7 +54,7 @@ def authenticate_with_token(name: str, token: str) -> bool:
if user is not None: if user is not None:
user_token = user_token_service.find_by_user_and_token(user, token) user_token = user_token_service.find_by_user_and_token(user, token)
if user is not None \ if user is not None \
and authentication_service.is_valid_token(user_token):
and user_token_service.is_valid_token(user_token):
g.user = user g.user = user
g.user_token = user_token g.user_token = user_token
return True return True

21
server/corvus/service/authentication_service.py

@ -1,6 +1,5 @@
"""Service to handle authentication.""" """Service to handle authentication."""
import re import re
from datetime import datetime
from typing import Optional from typing import Optional
from nacl import pwhash from nacl import pwhash
@ -53,26 +52,6 @@ def is_valid_password(user: User, password: str) -> bool:
return False return False
def is_valid_token(user_token: Optional[UserToken]) -> bool:
"""
Validate a token.
Token must be enabled and if it has an expiration, it must be greater
than now.
:param user_token:
:return:
"""
if user_token is None:
return False
if not user_token.enabled:
return False
if (user_token.expiration_time is not None
and user_token.expiration_time < datetime.utcnow()):
return False
return True
def logout(user_token: Optional[UserToken] = None) -> None: def logout(user_token: Optional[UserToken] = None) -> None:
""" """
Remove a user_token associated with a client session. Remove a user_token associated with a client session.

34
server/corvus/service/user_token_service.py

@ -3,6 +3,7 @@ import uuid
from datetime import datetime from datetime import datetime
from typing import Optional, Dict, Callable, Any, List from typing import Optional, Dict, Callable, Any, List
from flask_sqlalchemy import Pagination
from iso8601 import iso8601, ParseError from iso8601 import iso8601, ParseError
from corvus.db import db from corvus.db import db
@ -42,6 +43,7 @@ class UserTokenTransformer(BaseTransformer):
'expirationTime': self.serialize_expiration_time, 'expirationTime': self.serialize_expiration_time,
'creationTime': self.serialize_creation_time, 'creationTime': self.serialize_creation_time,
'lastUsageTime': self.serialize_last_usage_time, 'lastUsageTime': self.serialize_last_usage_time,
'isValid': self.serialize_is_valid,
'version': self.serialize_version 'version': self.serialize_version
} }
@ -108,6 +110,9 @@ class UserTokenTransformer(BaseTransformer):
"""User token last usage time.""" """User token last usage time."""
model.last_usage_time = iso8601.parse_date(last_usage_time) model.last_usage_time = iso8601.parse_date(last_usage_time)
def serialize_is_valid(self):
return is_valid_token(self.model)
def serialize_version(self) -> int: def serialize_version(self) -> int:
"""User token version.""" """User token version."""
return self.model.version return self.model.version
@ -161,6 +166,26 @@ def create(
return user_token return user_token
def is_valid_token(user_token: Optional[UserToken]) -> bool:
"""
Validate a token.
Token must be enabled and if it has an expiration, it must be greater
than now.
:param user_token:
:return:
"""
if user_token is None:
return False
if not user_token.enabled:
return False
if (user_token.expiration_time is not None
and user_token.expiration_time < datetime.utcnow()):
return False
return True
def delete(user_token: UserToken) -> bool: def delete(user_token: UserToken) -> bool:
""" """
Delete a user_token. Delete a user_token.
@ -186,11 +211,14 @@ def find_by_user_and_token(user: User, token: str) -> Optional[UserToken]:
return UserToken.query.filter_by(user_id=user.id, token=token).first() return UserToken.query.filter_by(user_id=user.id, token=token).first()
def find_by_user(user: User) -> List[UserToken]:
def find_by_user(user: User, page: int, per_page: int = 20, max_per_page: int = 100) -> Pagination:
""" """
Find all tokens for a user Find all tokens for a user
:param user:
:param user: The user to find tokens for
:param page: The page to request
:param per_page: The number to get per page
:param max_per_page:
:return: :return:
""" """
return UserToken.query.filter_by(user_id=user.id)
return UserToken.query.filter_by(user_id=user.id).paginate(page, per_page, True, max_per_page)
Loading…
Cancel
Save