diff --git a/CHANGELOG.md b/CHANGELOG.md index 2a862277..af6f52f4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## --- [4.2.2] - 2023/TBD ### New features TBD +### Refactor +- Remove deprecated API V1 ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/670)) ### Bug fixes - Remove webhook `custom` option from webook provider list as it's not currently an option ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/664)) ### Tweaks diff --git a/app/classes/web/api_handler.py b/app/classes/web/api_handler.py deleted file mode 100644 index 34b09ee8..00000000 --- a/app/classes/web/api_handler.py +++ /dev/null @@ -1,446 +0,0 @@ -from datetime import datetime -import logging -import re - -from app.classes.controllers.crafty_perms_controller import EnumPermissionsCrafty -from app.classes.controllers.server_perms_controller import EnumPermissionsServer -from app.classes.web.base_handler import BaseHandler -from app.classes.models.management import DatabaseShortcuts - -logger = logging.getLogger(__name__) -bearer_pattern = re.compile(r"^Bearer", flags=re.IGNORECASE) - - -class ApiHandler(BaseHandler): - def return_response(self, status: int, data: dict): - # Define a standardized response - self.set_status(status) - self.write(data) - - def check_xsrf_cookie(self): - # Disable CSRF protection on API routes - pass - - def access_denied(self, user, reason=""): - if reason: - reason = " because " + reason - logger.info( - "User %s from IP %s was denied access to the API route " - + self.request.path - + reason, - user, - self.get_remote_ip(), - ) - self.finish( - self.return_response( - 403, - { - "error": "ACCESS_DENIED", - "info": "You were denied access to the requested resource", - }, - ) - ) - - def authenticate_user(self) -> bool: - self.permissions = { - "Commands": EnumPermissionsServer.COMMANDS, - "Terminal": EnumPermissionsServer.TERMINAL, - "Logs": EnumPermissionsServer.LOGS, - "Schedule": EnumPermissionsServer.SCHEDULE, - "Backup": EnumPermissionsServer.BACKUP, - "Files": EnumPermissionsServer.FILES, - "Config": EnumPermissionsServer.CONFIG, - "Players": EnumPermissionsServer.PLAYERS, - "Server_Creation": EnumPermissionsCrafty.SERVER_CREATION, - "User_Config": EnumPermissionsCrafty.USER_CONFIG, - "Roles_Config": EnumPermissionsCrafty.ROLES_CONFIG, - } - try: - logger.debug("Searching for specified token") - - api_token = self.get_argument("token", "") - self.api_token = api_token - if api_token is None and self.request.headers.get("Authorization"): - api_token = bearer_pattern.sub( - "", self.request.headers.get("Authorization") - ) - elif api_token is None: - api_token = self.get_cookie("token") - user_data = self.controller.users.get_user_by_api_token(api_token) - - logger.debug("Checking results") - if user_data: - # Login successful! Check perms - logger.info(f"User {user_data['username']} has authenticated to API") - - return True # This is to set the "authenticated" - logging.debug("Auth unsuccessful") - self.access_denied("unknown", "the user provided an invalid token") - return False - except Exception as e: - logger.warning("An error occured while authenticating an API user: %s", e) - self.finish( - self.return_response( - 403, - { - "error": "ACCESS_DENIED", - "info": "An error occured while authenticating the user", - }, - ) - ) - return False - - -class ServersStats(ApiHandler): - def get(self): - """Get details about all servers""" - authenticated = self.authenticate_user() - user_obj = self.controller.users.get_user_by_api_token(self.api_token) - if not authenticated: - return - if user_obj["superuser"]: - raw_stats = self.controller.servers.get_all_servers_stats() - else: - raw_stats = self.controller.servers.get_authorized_servers_stats( - user_obj["user_id"] - ) - stats = [] - for rs in raw_stats: - s = {} - for k, v in rs["server_data"].items(): - if isinstance(v, datetime): - s[k] = v.timestamp() - else: - s[k] = v - stats.append(s) - - # Get server stats - # TODO Check perms - self.finish(self.write({"servers": stats})) - - -class NodeStats(ApiHandler): - def get(self): - """Get stats for particular node""" - authenticated = self.authenticate_user() - if not authenticated: - return - - # Get node stats - node_stats = self.controller.servers.stats.get_node_stats() - self.return_response(200, {"code": node_stats["node_stats"]}) - - -class SendCommand(ApiHandler): - def post(self): - user = self.authenticate_user() - - user_obj = self.controller.users.get_user_by_api_token(self.api_token) - - if user is None: - self.access_denied("unknown") - return - server_id = self.get_argument("id") - - if ( - not user_obj["user_id"] - in self.controller.server_perms.get_server_user_list(server_id) - and not user_obj["superuser"] - ): - self.access_denied("unknown") - return - - if not self.permissions[ - "Commands" - ] in self.controller.server_perms.get_api_key_permissions_list( - self.controller.users.get_api_key_by_token(self.api_token), server_id - ): - self.access_denied(user) - return - - command = self.get_argument("command", default=None, strip=True) - server_id = self.get_argument("id") - if command: - server = self.controller.servers.get_server_instance_by_id(server_id) - if server.check_running: - server.send_command(command) - self.return_response(200, {"run": True}) - else: - self.return_response(200, {"error": "SER_NOT_RUNNING"}) - else: - self.return_response(200, {"error": "NO_COMMAND"}) - - -class ServerBackup(ApiHandler): - def post(self): - user = self.authenticate_user() - - user_obj = self.controller.users.get_user_by_api_token(self.api_token) - - if user is None: - self.access_denied("unknown") - return - server_id = self.get_argument("id") - - if ( - not user_obj["user_id"] - in self.controller.server_perms.get_server_user_list(server_id) - and not user_obj["superuser"] - ): - self.access_denied("unknown") - return - - if not self.permissions[ - "Backup" - ] in self.controller.server_perms.get_api_key_permissions_list( - self.controller.users.get_api_key_by_token(self.api_token), server_id - ): - self.access_denied(user) - return - - server = self.controller.servers.get_server_instance_by_id(server_id) - - server.backup_server() - - self.return_response(200, {"code": "SER_BAK_CALLED"}) - - -class StartServer(ApiHandler): - def post(self): - user = self.authenticate_user() - remote_ip = self.get_remote_ip() - - user_obj = self.controller.users.get_user_by_api_token(self.api_token) - - if user is None: - self.access_denied("unknown") - return - server_id = self.get_argument("id") - - if ( - not user_obj["user_id"] - in self.controller.server_perms.get_server_user_list(server_id) - and not user_obj["superuser"] - ): - self.access_denied("unknown") - return - if not self.permissions[ - "Commands" - ] in self.controller.server_perms.get_api_key_permissions_list( - self.controller.users.get_api_key_by_token(self.api_token), server_id - ): - self.access_denied("unknown") - return - - server = self.controller.servers.get_server_instance_by_id(server_id) - - if not server.check_running(): - self.controller.management.send_command( - user_obj["user_id"], server_id, remote_ip, "start_server" - ) - self.return_response(200, {"code": "SER_START_CALLED"}) - else: - self.return_response(500, {"error": "SER_RUNNING"}) - - -class StopServer(ApiHandler): - def post(self): - user = self.authenticate_user() - remote_ip = self.get_remote_ip() - - user_obj = self.controller.users.get_user_by_api_token(self.api_token) - - if user is None: - self.access_denied("unknown") - return - server_id = self.get_argument("id") - - if ( - not user_obj["user_id"] - in self.controller.server_perms.get_server_user_list(server_id) - and not user_obj["superuser"] - ): - self.access_denied("unknown") - - if not self.permissions[ - "Commands" - ] in self.controller.server_perms.get_api_key_permissions_list( - self.controller.users.get_api_key_by_token(self.api_token), server_id - ): - self.access_denied(user) - return - - server = self.controller.servers.get_server_instance_by_id(server_id) - - if server.check_running(): - self.controller.management.send_command( - user, server_id, remote_ip, "stop_server" - ) - - self.return_response(200, {"code": "SER_STOP_CALLED"}) - else: - self.return_response(500, {"error": "SER_NOT_RUNNING"}) - - -class RestartServer(ApiHandler): - def post(self): - user = self.authenticate_user() - remote_ip = self.get_remote_ip() - user_obj = self.controller.users.get_user_by_api_token(self.api_token) - - if user is None: - self.access_denied("unknown") - return - server_id = self.get_argument("id") - - if not user_obj["user_id"] in self.controller.server_perms.get_server_user_list( - server_id - ): - self.access_denied("unknown") - - if not self.permissions[ - "Commands" - ] in self.controller.server_perms.get_api_key_permissions_list( - self.controller.users.get_api_key_by_token(self.api_token), server_id - ): - self.access_denied(user) - - self.controller.management.send_command( - user, server_id, remote_ip, "restart_server" - ) - self.return_response(200, {"code": "SER_RESTART_CALLED"}) - - -class CreateUser(ApiHandler): - def post(self): - user = self.authenticate_user() - user_obj = self.controller.users.get_user_by_api_token(self.api_token) - - user_perms = self.controller.crafty_perms.get_crafty_permissions_list( - user_obj["user_id"] - ) - if ( - not self.permissions["User_Config"] in user_perms - and not user_obj["superuser"] - ): - self.access_denied("unknown") - return - - if user is None: - self.access_denied("unknown") - return - - if not self.permissions[ - "User_Config" - ] in self.controller.crafty_perms.get_api_key_permissions_list( - self.controller.users.get_api_key_by_token(self.api_token) - ): - self.access_denied(user) - return - - new_username = self.get_argument("username").lower() - new_pass = self.get_argument("password") - manager = int(user_obj["user_id"]) - - if new_username: - self.controller.users.add_user( - new_username, manager, new_pass, "default@example.com", True, False - ) - - self.return_response( - 200, - { - "code": "COMPLETE", - "username": new_username, - "password": new_pass, - }, - ) - else: - self.return_response( - 500, - { - "error": "MISSING_PARAMS", - "info": "Some paramaters failed validation", - }, - ) - - -class DeleteUser(ApiHandler): - def post(self): - user = self.authenticate_user() - - user_obj = self.controller.users.get_user_by_api_token(self.api_token) - - user_perms = self.controller.crafty_perms.get_crafty_permissions_list( - user_obj["user_id"] - ) - - if ( - not self.permissions["User_Config"] in user_perms - and not user_obj["superuser"] - ): - self.access_denied("unknown") - return - - if user is None: - self.access_denied("unknown") - return - - if not self.permissions[ - "User_Config" - ] in self.controller.crafty_perms.get_api_key_permissions_list( - self.controller.users.get_api_key_by_token(self.api_token) - ): - self.access_denied(user) - return - - user_id = self.get_argument("user_id", None, True) - user_to_del = self.controller.users.get_user_by_id(user_id) - - if user_to_del["superuser"]: - self.return_response( - 500, - {"error": "NOT_ALLOWED", "info": "You cannot delete a super user"}, - ) - else: - if user_id: - self.controller.users.remove_user(user_id) - self.return_response(200, {"code": "COMPLETED"}) - - -class ListServers(ApiHandler): - def get(self): - user = self.authenticate_user() - user_obj = self.controller.users.get_user_by_api_token(self.api_token) - - if user is None: - self.access_denied("unknown") - return - - if self.api_token is None: - self.access_denied("unknown") - return - - if user_obj["superuser"]: - servers = self.controller.servers.get_all_defined_servers() - servers = [str(i) for i in servers] - else: - servers = self.controller.servers.get_authorized_servers( - user_obj["user_id"] - ) - page_servers = [] - for server in servers: - if server not in page_servers: - page_servers.append( - DatabaseShortcuts.get_data_obj(server.server_object) - ) - servers = page_servers - servers = [str(i) for i in servers] - - self.return_response( - 200, - { - "code": "COMPLETED", - "servers": servers, - }, - ) diff --git a/app/classes/web/tornado_handler.py b/app/classes/web/tornado_handler.py index 621c930a..f5501d31 100644 --- a/app/classes/web/tornado_handler.py +++ b/app/classes/web/tornado_handler.py @@ -22,18 +22,6 @@ from app.classes.web.default_handler import DefaultHandler from app.classes.web.routes.api.api_handlers import api_handlers from app.classes.web.routes.metrics.metrics_handlers import metrics_handlers from app.classes.web.server_handler import ServerHandler -from app.classes.web.api_handler import ( - ServersStats, - NodeStats, - ServerBackup, - StartServer, - StopServer, - RestartServer, - CreateUser, - DeleteUser, - ListServers, - SendCommand, -) from app.classes.web.websocket_handler import WebSocketHandler from app.classes.web.static_handler import CustomStaticHandler from app.classes.web.upload_handler import UploadHandler @@ -162,17 +150,6 @@ class Webserver: (r"/ws", WebSocketHandler, handler_args), (r"/upload", UploadHandler, handler_args), (r"/status", StatusHandler, handler_args), - # API Routes V1 - (r"/api/v1/stats/servers", ServersStats, handler_args), - (r"/api/v1/stats/node", NodeStats, handler_args), - (r"/api/v1/server/send_command", SendCommand, handler_args), - (r"/api/v1/server/backup", ServerBackup, handler_args), - (r"/api/v1/server/start", StartServer, handler_args), - (r"/api/v1/server/stop", StopServer, handler_args), - (r"/api/v1/server/restart", RestartServer, handler_args), - (r"/api/v1/list_servers", ListServers, handler_args), - (r"/api/v1/users/create_user", CreateUser, handler_args), - (r"/api/v1/users/delete_user", DeleteUser, handler_args), # API Routes V2 *api_handlers(handler_args), # API Routes OpenMetrics