Added sessions support.

This commit is contained in:
Daniele Maglie 2024-01-21 14:18:57 +01:00
parent 183fa3e27a
commit 0fb6d08a7f
9 changed files with 115 additions and 60 deletions

View file

@ -23,17 +23,19 @@ class ErrorCode:
TOKEN_FORMAT_NON_VALID = '1008' TOKEN_FORMAT_NON_VALID = '1008'
NON_VALID_PARAMETERS = '1009' TOKEN_EXPIRED = '1009'
MISSING_PARAMETERS = '1010' NON_VALID_PARAMETERS = '1010'
TOO_MANY_PARAMETERS = '1011' MISSING_PARAMETERS = '1011'
MISSING_FILE = '1012' TOO_MANY_PARAMETERS = '1012'
CHANGE_PASSWORD = '1013' MISSING_FILE = '1013'
IP_ADDRESS_BLACKLISTED = '1014' CHANGE_PASSWORD = '1014'
IP_ADDRESS_BLACKLISTED = '1015'
class ErrorMessage: class ErrorMessage:
@ -63,6 +65,8 @@ class ErrorMessage:
TOKEN_FORMAT_NON_VALID = 'Token format non valid.' TOKEN_FORMAT_NON_VALID = 'Token format non valid.'
TOKEN_EXPIRED = 'This token is expired.'
# json POST parameters # json POST parameters
NON_VALID_PARAMETERS = 'Expecting JSON data.' NON_VALID_PARAMETERS = 'Expecting JSON data.'
@ -77,6 +81,6 @@ class ErrorMessage:
# account settings # account settings
CHANGE_PASSWORD = 'A password change has been activated for your account. You must first authenticate via web app and follow the instructions.' CHANGE_PASSWORD = 'A password change has been activated for your account. You must authenticate via web interface and follow the instructions. You should now discard this tokens pairs.'
IP_ADDRESS_BLACKLISTED = 'Your IP address is temporary blacklisted.' IP_ADDRESS_BLACKLISTED = 'Your IP address is temporary blacklisted.'

View file

@ -2,6 +2,8 @@ from .base import BaseHandler
from piracyshield_component.exception import ApplicationException from piracyshield_component.exception import ApplicationException
from piracyshield_service.security.blacklist.exists_by_access_token import SecurityBlacklistExistsByAccessTokenService
from piracyshield_service.authentication.verify_access_token import AuthenticationVerifyAccessTokenService from piracyshield_service.authentication.verify_access_token import AuthenticationVerifyAccessTokenService
from piracyshield_service.permission.service import PermissionService from piracyshield_service.permission.service import PermissionService
@ -28,6 +30,8 @@ class ProtectedHandler(BaseHandler):
account_data = {} account_data = {}
async def prepare(self): async def prepare(self):
self.security_blacklist_exists_by_access_token_service = SecurityBlacklistExistsByAccessTokenService()
self.authentication_verify_access_token_service = AuthenticationVerifyAccessTokenService() self.authentication_verify_access_token_service = AuthenticationVerifyAccessTokenService()
await super().prepare() await super().prepare()
@ -52,12 +56,19 @@ class ProtectedHandler(BaseHandler):
return False return False
# get the token only # get the token only
token = authorization_header[7:] self.current_access_token = authorization_header[7:]
try: try:
if self.security_blacklist_exists_by_access_token_service.execute(
access_token = self.current_access_token
) == True:
self.error(status_code = 401, error_code = ErrorCode.TOKEN_EXPIRED, message = ErrorMessage.TOKEN_EXPIRED)
return False
# set the current account data # set the current account data
# TODO: absolutely need to validate the payload. # TODO: absolutely need to validate the payload.
self.account_data = self.authentication_verify_access_token_service.execute(token) self.account_data = self.authentication_verify_access_token_service.execute(self.current_access_token)
if not self.account_data.get('email'): if not self.account_data.get('email'):
self.error(status_code = 401, error_code = ErrorCode.TOKEN_FORMAT_NON_VALID, message = ErrorMessage.TOKEN_FORMAT_NON_VALID) self.error(status_code = 401, error_code = ErrorCode.TOKEN_FORMAT_NON_VALID, message = ErrorMessage.TOKEN_FORMAT_NON_VALID)

View file

@ -15,7 +15,7 @@ class TestReporterCreateTicket:
ticket_wait_time = 76 ticket_wait_time = 76
ticket_parameters = { ticket_parameters = {
'dda_id': '2326485749e94573bf5724ff5006f30c', 'dda_id': '002ad48ea02a43db9003b4f15f1da9b3',
'description': '__MOCK_TICKET__', 'description': '__MOCK_TICKET__',
'forensic_evidence': { 'forensic_evidence': {
'hash': {} 'hash': {}
@ -44,6 +44,8 @@ class TestReporterCreateTicket:
create_response = authenticated_post_request('/api/v1/ticket/create', self.access_token, self.ticket_parameters) create_response = authenticated_post_request('/api/v1/ticket/create', self.access_token, self.ticket_parameters)
print(" RES -> ", create_response.json())
assert create_response.status_code == 200 assert create_response.status_code == 200
assert create_response.json()['status'] == 'success' assert create_response.json()['status'] == 'success'

View file

@ -4,9 +4,10 @@ import pytest
import os import os
import requests import requests
application_config = Config('api').get('general') application_config = Config('application').get('general')
api_config = Config('application').get('api')
URL = f"http://127.0.0.1:{application_config['port']}" URL = f"{application_config.get('domain')}:{api_config.get('port')}"
def get_request(endpoint: str): def get_request(endpoint: str):
return requests.get(f'{URL}{endpoint}') return requests.get(f'{URL}{endpoint}')

View file

@ -0,0 +1,41 @@
import sys
import os
# I hate python imports
current = os.path.dirname(os.path.realpath(__file__))
parent = os.path.dirname(current)
sys.path.append(parent)
import tornado
from ioutils.protected import ProtectedHandler
from piracyshield_service.permission.service import PermissionService
from piracyshield_service.account.session.get_all_by_account_ordered import AccountSessionGetAllByAccountOrderedService
from piracyshield_component.exception import ApplicationException
class GetAllSessionAccountHandler(ProtectedHandler):
"""
Handles getting multiple active sessions.
"""
async def get(self):
if self.initialize_account() == False:
return
try:
account_session_get_all_by_account_ordered_service = AccountSessionGetAllByAccountOrderedService()
response = await tornado.ioloop.IOLoop.current().run_in_executor(
None,
account_session_get_all_by_account_ordered_service.execute,
self.account_data.get('account_id'),
self.current_access_token
)
self.success(data = response)
except ApplicationException as e:
self.error(status_code = 400, error_code = e.code, message = e.message)

View file

@ -11,8 +11,6 @@ from ioutils.base import BaseHandler
from ioutils.errors import ErrorCode, ErrorMessage from ioutils.errors import ErrorCode, ErrorMessage
from piracyshield_service.authentication.authenticate import AuthenticationAuthenticateService from piracyshield_service.authentication.authenticate import AuthenticationAuthenticateService
from piracyshield_service.authentication.generate_access_token import AuthenticationGenerateAccessTokenService
from piracyshield_service.authentication.generate_refresh_token import AuthenticationGenerateRefreshTokenService
from piracyshield_component.exception import ApplicationException from piracyshield_component.exception import ApplicationException
@ -36,14 +34,19 @@ class AuthenticationLoginHandler(BaseHandler):
return return
try: try:
authentication_authenticate_service = AuthenticationAuthenticateService()
access_token, refresh_token = await tornado.ioloop.IOLoop.current().run_in_executor( access_token, refresh_token = await tornado.ioloop.IOLoop.current().run_in_executor(
None, None,
self.process, authentication_authenticate_service.execute,
self.request_data.get('email'), self.request_data.get('email'),
self.request_data.get('password'), self.request_data.get('password'),
self.request.remote_ip self.request.remote_ip
) )
# store the refresh token in a http-only secure cookie
self.set_refresh_cookie(value = refresh_token)
self.success(data = { self.success(data = {
'access_token': access_token, 'access_token': access_token,
'refresh_token': refresh_token 'refresh_token': refresh_token
@ -51,25 +54,3 @@ class AuthenticationLoginHandler(BaseHandler):
except ApplicationException as e: except ApplicationException as e:
self.error(status_code = 400, error_code = e.code, message = e.message) self.error(status_code = 400, error_code = e.code, message = e.message)
def process(self, email: str, password: str, ip_address: str) -> tuple:
authentication_authenticate_service = AuthenticationAuthenticateService()
# try to authenticate
payload = authentication_authenticate_service.execute(
email = email,
password = password,
ip_address = ip_address
)
authentication_generate_access_token_service = AuthenticationGenerateAccessTokenService()
authentication_generate_refresh_token_service = AuthenticationGenerateRefreshTokenService()
# generate token pairs
access_token = authentication_generate_access_token_service.execute(payload)
refresh_token = authentication_generate_refresh_token_service.execute(payload)
# store the refresh_token in a http-only cookie
self.set_refresh_cookie(value = refresh_token)
return access_token, refresh_token

View file

@ -7,19 +7,31 @@ sys.path.append(parent)
from ioutils.protected import ProtectedHandler from ioutils.protected import ProtectedHandler
from piracyshield_service.account.session.destroy_current_sessions import AccountSessionDestroyCurrentSessionsService
from piracyshield_component.exception import ApplicationException from piracyshield_component.exception import ApplicationException
class AuthenticationLogoutHandler(ProtectedHandler): class AuthenticationLogoutHandler(ProtectedHandler):
""" """
Removes the authentication refresh token. Removes the authentication refresh token and blacklists both the tokens.
The effective logout remains on the access token expiration time, this is why it should be set to a short time.
""" """
def get(self): def get(self):
if self.initialize_account() == False: if self.initialize_account() == False:
return return
self.clear_cookie('refresh_token') try:
account_session_destroy_current_sessions_service = AccountSessionDestroyCurrentSessionsService()
self.success(data = 'Goodbye!') account_session_destroy_current_sessions_service.execute(
self.account_data.get('account_id'),
self.current_access_token
)
self.clear_cookie('refresh_token')
self.success(data = 'Goodbye!')
except ApplicationException as e:
self.error(status_code = 400, error_code = e.code, message = e.message)

View file

@ -10,8 +10,9 @@ import tornado
from ioutils.base import BaseHandler from ioutils.base import BaseHandler
from ioutils.errors import ErrorCode, ErrorMessage from ioutils.errors import ErrorCode, ErrorMessage
from piracyshield_service.authentication.verify_refresh_token import AuthenticationVerifyRefreshTokenService from piracyshield_service.security.blacklist.exists_by_refresh_token import SecurityBlacklistExistsByRefreshTokenService
from piracyshield_service.authentication.generate_access_token import AuthenticationGenerateAccessTokenService
from piracyshield_service.authentication.refresh_access_token import AuthenticationRefreshAccessTokenService
from piracyshield_component.exception import ApplicationException from piracyshield_component.exception import ApplicationException
@ -41,10 +42,22 @@ class AuthenticationRefreshHandler(BaseHandler):
refresh_token = self.request_data.get('refresh_token') refresh_token = self.request_data.get('refresh_token')
try: try:
security_blacklist_exists_by_refresh_token_service = SecurityBlacklistExistsByRefreshTokenService()
if security_blacklist_exists_by_refresh_token_service.execute(
refresh_token = refresh_token
) == True:
self.error(status_code = 401, error_code = ErrorCode.TOKEN_EXPIRED, message = ErrorMessage.TOKEN_EXPIRED)
return False
authentication_refresh_access_token_service = AuthenticationRefreshAccessTokenService()
access_token = await tornado.ioloop.IOLoop.current().run_in_executor( access_token = await tornado.ioloop.IOLoop.current().run_in_executor(
None, None,
self.process, authentication_refresh_access_token_service.execute,
refresh_token refresh_token,
self.request.remote_ip
) )
# return the access_token # return the access_token
@ -54,18 +67,3 @@ class AuthenticationRefreshHandler(BaseHandler):
except ApplicationException as e: except ApplicationException as e:
self.error(status_code = 400, error_code = e.code, message = e.message) self.error(status_code = 400, error_code = e.code, message = e.message)
def process(self, refresh_token: str) -> str:
authentication_verify_refresh_token_service = AuthenticationVerifyRefreshTokenService()
# verify the token and unwrap the payload
payload = authentication_verify_refresh_token_service.execute(
token = refresh_token
)
authentication_generate_access_token_service = AuthenticationGenerateAccessTokenService()
# generate a new access token
access_token = authentication_generate_access_token_service.execute(payload)
return access_token

View file

@ -31,6 +31,8 @@ from .handlers.account.provider.set_status import SetStatusActiveProviderAccount
from .handlers.account.provider.change_password import ChangePasswordProviderAccountHandler from .handlers.account.provider.change_password import ChangePasswordProviderAccountHandler
from .handlers.account.provider.remove import RemoveProviderAccountHandler from .handlers.account.provider.remove import RemoveProviderAccountHandler
from .handlers.account.session.get_all import GetAllSessionAccountHandler
from .handlers.ticket.create import CreateTicketHandler from .handlers.ticket.create import CreateTicketHandler
from .handlers.ticket.get import GetTicketHandler from .handlers.ticket.get import GetTicketHandler
from .handlers.ticket.get_all import GetAllTicketHandler from .handlers.ticket.get_all import GetAllTicketHandler
@ -139,6 +141,9 @@ class APIv1:
(r"/account/provider/change_password", ChangePasswordProviderAccountHandler), (r"/account/provider/change_password", ChangePasswordProviderAccountHandler),
(r"/account/provider/remove", RemoveProviderAccountHandler), (r"/account/provider/remove", RemoveProviderAccountHandler),
# sessions
(r"/account/session/get/all", GetAllSessionAccountHandler),
# blocking ticket management # blocking ticket management
(r"/ticket/create", CreateTicketHandler), (r"/ticket/create", CreateTicketHandler),
(r"/ticket/get", GetTicketHandler), (r"/ticket/get", GetTicketHandler),