diff --git a/.gitlab/lint.yml b/.gitlab/lint.yml
index bc797808..37649e1a 100644
--- a/.gitlab/lint.yml
+++ b/.gitlab/lint.yml
@@ -5,7 +5,7 @@ yamllint:
stage: lint
image: registry.gitlab.com/pipeline-components/yamllint:latest
tags:
- - docker
+ - saas-linux-medium-amd64
rules:
- if: "$CODE_QUALITY_DISABLED"
when: never
@@ -18,7 +18,7 @@ jsonlint:
stage: lint
image: registry.gitlab.com/pipeline-components/jsonlint:latest
tags:
- - docker
+ - saas-linux-medium-amd64
rules:
- if: "$CODE_QUALITY_DISABLED"
when: never
@@ -33,7 +33,7 @@ black:
stage: lint
image: registry.gitlab.com/pipeline-components/black:latest
tags:
- - docker
+ - saas-linux-medium-amd64
rules:
- if: "$CODE_QUALITY_DISABLED"
when: never
@@ -46,7 +46,7 @@ pylint:
stage: lint
image: registry.gitlab.com/pipeline-components/pylint:latest
tags:
- - docker
+ - saas-linux-medium-amd64
rules:
- if: "$CODE_QUALITY_DISABLED"
when: never
@@ -69,7 +69,7 @@ sonarcloud-check:
name: sonarsource/sonar-scanner-cli:latest
entrypoint: [""]
tags:
- - docker
+ - saas-linux-medium-amd64
rules:
- if: "$SONAR_TOKEN == null"
when: never
@@ -91,7 +91,7 @@ lang-check:
stage: lint
image: alpine:latest
tags:
- - docker
+ - saas-linux-medium-amd64
rules:
- if: "$CODE_QUALITY_DISABLED"
when: never
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 145d869b..c7ed7771 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,4 +1,18 @@
# Changelog
+## --- [4.4.0] - 2024/05/11
+### Refactor
+- Refactor API keys "super user" to "full access" ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/731) | [Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/759))
+- Refactor SBuilder to use Big Bucket Svc ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/755))
+### Bug fixes
+- Reset query arguments on login if `?next` is not available ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/750))
+- Fix child schedule failing to load after del parent ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/753))
+### Tweaks
+- Add link to go back to dashboard on error page ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/743))
+- Set audit logging to logfile instead of DB ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/751))
+### Lang
+- Changes of phrase in `cs_CS` translation ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/749))
+
+
## --- [4.3.2] - 2024/04/07
### Refactor
- Refactor ServerJars caching and move to api.serverjars.com ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/744) | [Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/746))
diff --git a/README.md b/README.md
index ae70bd0e..8c70fdb4 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,5 @@
[![Crafty Logo](app/frontend/static/assets/images/logo_long.svg)](https://craftycontrol.com)
-# Crafty Controller 4.3.2
+# Crafty Controller 4.4.0
> Python based Control Panel for your Minecraft Server
## What is Crafty Controller?
diff --git a/app/classes/controllers/management_controller.py b/app/classes/controllers/management_controller.py
index 7085b503..cab755b6 100644
--- a/app/classes/controllers/management_controller.py
+++ b/app/classes/controllers/management_controller.py
@@ -95,9 +95,6 @@ class ManagementController:
# **********************************************************************************
# Audit_Log Methods
# **********************************************************************************
- @staticmethod
- def get_activity_log():
- return HelpersManagement.get_activity_log()
def add_to_audit_log(self, user_id, log_msg, server_id=None, source_ip=None):
return self.management_helper.add_to_audit_log(
diff --git a/app/classes/controllers/server_perms_controller.py b/app/classes/controllers/server_perms_controller.py
index 37893e9e..4586b4aa 100644
--- a/app/classes/controllers/server_perms_controller.py
+++ b/app/classes/controllers/server_perms_controller.py
@@ -17,6 +17,10 @@ class ServerPermsController:
def get_server_user_list(server_id):
return PermissionsServers.get_server_user_list(server_id)
+ @staticmethod
+ def get_permissions(permissions_mask):
+ return PermissionsServers.get_permissions(permissions_mask)
+
@staticmethod
def list_defined_permissions():
permissions_list = PermissionsServers.get_permissions_list()
@@ -61,6 +65,22 @@ class ServerPermsController:
def get_permissions_mask(role_id, server_id):
return PermissionsServers.get_permissions_mask(role_id, server_id)
+ @staticmethod
+ def get_lowest_api_perm_mask(user_server_permissions_mask, api_key_permssions_mask):
+ mask = ""
+ # If this isn't an API key we'll know the request came from basic
+ # authentication and ignore the API key permissions mask.
+ if not api_key_permssions_mask:
+ return user_server_permissions_mask
+ for _index, (user_perm, api_perm) in enumerate(
+ zip(user_server_permissions_mask, api_key_permssions_mask)
+ ):
+ if user_perm == "1" and api_perm == "1":
+ mask += "1"
+ else:
+ mask += "0"
+ return mask
+
@staticmethod
def set_permission(
permission_mask, permission_tested: EnumPermissionsServer, value
@@ -82,6 +102,11 @@ class ServerPermsController:
def get_api_key_permissions_list(key: ApiKeys, server_id: str):
return PermissionsServers.get_api_key_permissions_list(key, server_id)
+ @staticmethod
+ def get_user_permissions_mask(user_id: str, server_id: str):
+ user = HelperUsers.get_user_model(user_id)
+ return PermissionsServers.get_user_permissions_mask(user, server_id)
+
@staticmethod
def get_authorized_servers_stats_from_roles(user_id):
user_roles = HelperUsers.get_user_roles_id(user_id)
diff --git a/app/classes/logging/log_formatter.py b/app/classes/logging/log_formatter.py
new file mode 100644
index 00000000..e3f2b4f7
--- /dev/null
+++ b/app/classes/logging/log_formatter.py
@@ -0,0 +1,53 @@
+import logging
+import logging.config
+import json
+from datetime import datetime
+
+
+class JsonEncoderStrFallback(json.JSONEncoder):
+ def default(self, o):
+ try:
+ return super().default(o)
+ except TypeError as exc:
+ if "not JSON serializable" in str(exc):
+ return str(o)
+ raise
+
+
+class JsonEncoderDatetime(JsonEncoderStrFallback):
+ def default(self, o):
+ if isinstance(o, datetime):
+ return o.strftime("%Y-%m-%dT%H:%M:%S%z")
+
+ return super().default(o)
+
+
+class JsonFormatter(logging.Formatter):
+ def formatTime(self, record, datefmt=None):
+ """
+ Override formatTime to customize the time format.
+ """
+ timestamp = datetime.fromtimestamp(record.created)
+ if datefmt:
+ # Use the specified date format
+ return timestamp.strftime(datefmt)
+ # Default date format: YYYY-MM-DD HH:MM:SS,mmm
+ secs = int(record.msecs)
+ return f"{timestamp.strftime('%Y-%m-%d %H:%M:%S')},{secs:03d}"
+
+ def format(self, record):
+ log_data = {
+ "level": record.levelname,
+ "time": self.formatTime(record),
+ "log_msg": record.getMessage(),
+ }
+
+ # Filter out standard log record attributes and include only custom ones
+ custom_attrs = ["user_name", "user_id", "server_id", "source_ip"]
+ extra_attrs = {
+ key: value for key, value in record.__dict__.items() if key in custom_attrs
+ }
+
+ # Merge extra attributes with log data
+ log_data.update(extra_attrs)
+ return json.dumps(log_data)
diff --git a/app/classes/minecraft/bigbucket.py b/app/classes/minecraft/bigbucket.py
new file mode 100644
index 00000000..591aa6d8
--- /dev/null
+++ b/app/classes/minecraft/bigbucket.py
@@ -0,0 +1,236 @@
+import os
+import json
+import threading
+import time
+import logging
+from datetime import datetime
+import requests
+
+from app.classes.controllers.servers_controller import ServersController
+from app.classes.models.server_permissions import PermissionsServers
+from app.classes.shared.file_helpers import FileHelpers
+from app.classes.shared.websocket_manager import WebSocketManager
+
+logger = logging.getLogger(__name__)
+# Temp type var until sjars restores generic fetchTypes0
+
+
+class BigBucket:
+ def __init__(self, helper):
+ self.helper = helper
+ # remove any trailing slash from config.json
+ # url since we add it on all the calls
+ self.base_url = str(
+ self.helper.get_setting("big_bucket_repo", "https://jars.arcadiatech.org")
+ ).rstrip("/")
+
+ def _read_cache(self) -> dict:
+ cache_file = self.helper.big_bucket_cache
+ cache = {}
+ try:
+ with open(cache_file, "r", encoding="utf-8") as f:
+ cache = json.load(f)
+
+ except Exception as e:
+ logger.error(f"Unable to read big_bucket cache file: {e}")
+
+ return cache
+
+ def get_bucket_data(self):
+ data = self._read_cache()
+ return data.get("categories")
+
+ def _check_bucket_alive(self) -> bool:
+ logger.info("Checking Big Bucket status")
+
+ check_url = f"{self.base_url}/healthcheck"
+ try:
+ response = requests.get(check_url, timeout=2)
+ response_json = response.json()
+ if (
+ response.status_code in [200, 201]
+ and response_json.get("status") == "ok"
+ ):
+ logger.info("Big bucket is alive and responding as expected")
+ return True
+ except Exception as e:
+ logger.error(f"Unable to connect to big bucket due to error: {e}")
+ return False
+
+ logger.error(
+ "Big bucket manifest is not available as expected or unable to contact"
+ )
+ return False
+
+ def _get_big_bucket(self) -> dict:
+ logger.debug("Calling for big bucket manifest.")
+ try:
+ response = requests.get(f"{self.base_url}/manifest.json", timeout=5)
+ if response.status_code in [200, 201]:
+ data = response.json()
+ del data["manifest_version"]
+ return data
+ return {}
+ except TimeoutError as e:
+ logger.error(f"Unable to get jars from remote with error {e}")
+ return {}
+
+ def _refresh_cache(self):
+ """
+ Contains the shared logic for refreshing the cache.
+ This method is called by both manual_refresh_cache and refresh_cache methods.
+ """
+ if not self._check_bucket_alive():
+ logger.error("big bucket API is not available.")
+ return False
+
+ cache_data = {
+ "last_refreshed": datetime.now().strftime("%m/%d/%Y, %H:%M:%S"),
+ "categories": self._get_big_bucket(),
+ }
+ try:
+ with open(
+ self.helper.big_bucket_cache, "w", encoding="utf-8"
+ ) as cache_file:
+ json.dump(cache_data, cache_file, indent=4)
+ logger.info("Cache file successfully refreshed manually.")
+ except Exception as e:
+ logger.error(f"Failed to update cache file manually: {e}")
+
+ def manual_refresh_cache(self):
+ """
+ Manually triggers the cache refresh process.
+ """
+ logger.info("Manual bucket cache refresh initiated.")
+ self._refresh_cache()
+ logger.info("Manual refresh completed.")
+
+ def refresh_cache(self):
+ """
+ Automatically trigger cache refresh process based age.
+
+ This method checks if the cache file is older than a specified number of days
+ before deciding to refresh.
+ """
+ cache_file_path = self.helper.big_bucket_cache
+
+ # Determine if the cache is old and needs refreshing
+ cache_old = self.helper.is_file_older_than_x_days(cache_file_path)
+
+ # debug override
+ # cache_old = True
+
+ if not self._check_bucket_alive():
+ logger.error("big bucket API is not available.")
+ return False
+
+ if not cache_old:
+ logger.info("Cache file is not old enough to require automatic refresh.")
+ return False
+
+ logger.info("Automatic cache refresh initiated due to old cache.")
+ self._refresh_cache()
+
+ def get_fetch_url(self, jar, server, version) -> str:
+ """
+ Constructs the URL for downloading a server JAR file based on the server type.
+ Parameters:
+ jar (str): The category of the JAR file to download.
+ server (str): Server software name (e.g., "paper").
+ version (str): Server version.
+
+ Returns:
+ str or None: URL for downloading the JAR file, or None if URL cannot be
+ constructed or an error occurs.
+ """
+ try:
+ # Read cache file for URL that is in a list of one item
+ return self.get_bucket_data()[jar]["types"][server]["versions"][version][
+ "url"
+ ][0]
+ except Exception as e:
+ logger.error(f"An error occurred while constructing fetch URL: {e}")
+ return None
+
+ def download_jar(self, jar, server, version, path, server_id):
+ update_thread = threading.Thread(
+ name=f"server_download-{server_id}-{server}-{version}",
+ target=self.a_download_jar,
+ daemon=True,
+ args=(jar, server, version, path, server_id),
+ )
+ update_thread.start()
+
+ def a_download_jar(self, jar, server, version, path, server_id):
+ """
+ Downloads a server JAR file and performs post-download actions including
+ notifying users and setting import status.
+
+ This method waits for the server registration to complete, retrieves the
+ download URL for the specified server JAR file.
+
+ Upon successful download, it either runs the installer for
+ Forge servers or simply finishes the import process for other types. It
+ notifies server users about the completion of the download.
+
+ Parameters:
+ - jar (str): The category of the JAR file to download.
+ - server (str): The type of server software (e.g., 'forge', 'paper').
+ - version (str): The version of the server software.
+ - path (str): The local filesystem path where the JAR file will be saved.
+ - server_id (str): The unique identifier for the server being updated or
+ imported, used for notifying users and setting the import status.
+
+ Returns:
+ - bool: True if the JAR file was successfully downloaded and saved;
+ False otherwise.
+
+ The method ensures that the server is properly registered before proceeding
+ with the download and handles exceptions by logging errors and reverting
+ the import status if necessary.
+ """
+ # delaying download for server register to finish
+ time.sleep(3)
+
+ fetch_url = self.get_fetch_url(jar, server, version)
+ if not fetch_url:
+ return False
+
+ server_users = PermissionsServers.get_server_user_list(server_id)
+
+ # Make sure the server is registered before updating its stats
+ while True:
+ try:
+ ServersController.set_import(server_id)
+ for user in server_users:
+ WebSocketManager().broadcast_user(user, "send_start_reload", {})
+ break
+ except Exception as ex:
+ logger.debug(f"Server not registered yet. Delaying download - {ex}")
+
+ # Initiate Download
+ jar_dir = os.path.dirname(path)
+ jar_name = os.path.basename(path)
+ logger.info(fetch_url)
+ success = FileHelpers.ssl_get_file(fetch_url, jar_dir, jar_name)
+
+ # Post-download actions
+ if success:
+ if server == "forge-installer":
+ # If this is the newer Forge version, run the installer
+ ServersController.finish_import(server_id, True)
+ else:
+ ServersController.finish_import(server_id)
+
+ # Notify users
+ for user in server_users:
+ WebSocketManager().broadcast_user(
+ user, "notification", "Executable download finished"
+ )
+ time.sleep(3) # Delay for user notification
+ WebSocketManager().broadcast_user(user, "send_start_reload", {})
+ else:
+ logger.error(f"Unable to save jar to {path} due to download failure.")
+ ServersController.finish_import(server_id)
+
+ return success
diff --git a/app/classes/minecraft/serverjars.py b/app/classes/minecraft/serverjars.py
deleted file mode 100644
index 944ec382..00000000
--- a/app/classes/minecraft/serverjars.py
+++ /dev/null
@@ -1,395 +0,0 @@
-import os
-import json
-import threading
-import time
-import logging
-from datetime import datetime
-import requests
-
-from app.classes.controllers.servers_controller import ServersController
-from app.classes.models.server_permissions import PermissionsServers
-from app.classes.shared.file_helpers import FileHelpers
-from app.classes.shared.websocket_manager import WebSocketManager
-
-logger = logging.getLogger(__name__)
-# Temp type var until sjars restores generic fetchTypes0
-SERVERJARS_TYPES = ["modded", "proxies", "servers", "vanilla"]
-PAPERJARS = ["paper", "folia"]
-
-
-class ServerJars:
- def __init__(self, helper):
- self.helper = helper
- self.base_url = "https://api.serverjars.com"
- self.paper_base = "https://api.papermc.io"
-
- @staticmethod
- def get_paper_jars():
- return PAPERJARS
-
- def get_paper_versions(self, project):
- """
- Retrieves a list of versions for a specified project from the PaperMC API.
-
- Parameters:
- project (str): The project name to query for available versions.
-
- Returns:
- list: A list of version strings available for the project. Returns an empty
- list if the API call fails or if no versions are found.
-
- This function makes a GET request to the PaperMC API to fetch available project
- versions, The versions are returned in reverse order, with the most recent
- version first.
- """
- try:
- response = requests.get(
- f"{self.paper_base}/v2/projects/{project}/", timeout=2
- )
- response.raise_for_status()
- api_data = response.json()
- except Exception as e:
- logger.error(f"Error loading project versions for {project}: {e}")
- return []
-
- versions = api_data.get("versions", [])
- versions.reverse() # Ensure the most recent version comes first
- return versions
-
- def get_paper_build(self, project, version):
- """
- Fetches the latest build for a specified project and version from PaperMC API.
-
- Parameters:
- project (str): Project name, typically a server software like 'paper'.
- version (str): Project version to fetch the build number for.
-
- Returns:
- int or None: Latest build number if successful, None if not or on error.
-
- This method attempts to query the PaperMC API for the latest build and
- handles exceptions by logging errors and returning None.
- """
- try:
- response = requests.get(
- f"{self.paper_base}/v2/projects/{project}/versions/{version}/builds/",
- timeout=2,
- )
- response.raise_for_status()
- api_data = response.json()
- except Exception as e:
- logger.error(f"Error fetching build for {project} {version}: {e}")
- return None
-
- builds = api_data.get("builds", [])
- return builds[-1] if builds else None
-
- def _read_cache(self):
- cache_file = self.helper.serverjar_cache
- cache = {}
- try:
- with open(cache_file, "r", encoding="utf-8") as f:
- cache = json.load(f)
-
- except Exception as e:
- logger.error(f"Unable to read serverjars.com cache file: {e}")
-
- return cache
-
- def get_serverjar_data(self):
- data = self._read_cache()
- return data.get("types")
-
- def _check_sjars_api_alive(self):
- logger.info("Checking serverjars.com API status")
-
- check_url = f"{self.base_url}"
- try:
- response = requests.get(check_url, timeout=2)
- response_json = response.json()
-
- if (
- response.status_code in [200, 201]
- and response_json.get("status") == "success"
- and response_json.get("response", {}).get("status") == "ok"
- ):
- logger.info("Serverjars.com API is alive and responding as expected")
- return True
- except Exception as e:
- logger.error(f"Unable to connect to serverjar.com API due to error: {e}")
- return False
-
- logger.error(
- "Serverjars.com API is not responding as expected or unable to contact"
- )
- return False
-
- def _fetch_projects_for_type(self, server_type):
- """
- Fetches projects for a given server type from the ServerJars API.
- """
- try:
- response = requests.get(
- f"{self.base_url}/api/fetchTypes/{server_type}", timeout=5
- )
- response.raise_for_status() # Ensure HTTP errors are caught
- data = response.json()
- if data.get("status") == "success":
- return data["response"].get("servers", [])
- except requests.RequestException as e:
- print(f"Error fetching projects for type {server_type}: {e}")
- return []
-
- def _get_server_type_list(self):
- """
- Builds the type structure with projects fetched for each type.
- """
- type_structure = {}
- for server_type in SERVERJARS_TYPES:
- projects = self._fetch_projects_for_type(server_type)
- type_structure[server_type] = {project: [] for project in projects}
- return type_structure
-
- def _get_jar_versions(self, server_type, project_name, max_ver=50):
- """
- Grabs available versions for specified project
-
- Args:
- server_type (str): Server Type Category (modded, servers, etc)
- project_name (str): Target project (paper, forge, magma, etc)
- max (int, optional): Max versions returned. Defaults to 50.
-
- Returns:
- list: An array of versions
- """
- url = f"{self.base_url}/api/fetchAll/{server_type}/{project_name}?max={max_ver}"
- try:
- response = requests.get(url, timeout=5)
- response.raise_for_status() # Ensure HTTP errors are caught
- data = response.json()
- logger.debug(f"Received data for {server_type}/{project_name}: {data}")
-
- if data.get("status") == "success":
- versions = [
- item.get("version")
- for item in data.get("response", [])
- if "version" in item
- ]
- versions.reverse() # Reverse so versions are newest -> oldest
- logger.debug(f"Versions extracted: {versions}")
- return versions
- except requests.RequestException as e:
- logger.error(
- f"Error fetching jar versions for {server_type}/{project_name}: {e}"
- )
-
- return []
-
- def _refresh_cache(self):
- """
- Contains the shared logic for refreshing the cache.
- This method is called by both manual_refresh_cache and refresh_cache methods.
- """
- now = datetime.now()
- cache_data = {
- "last_refreshed": now.strftime("%m/%d/%Y, %H:%M:%S"),
- "types": self._get_server_type_list(),
- }
-
- for server_type, projects in cache_data["types"].items():
- for project_name in projects:
- versions = self._get_jar_versions(server_type, project_name)
- cache_data["types"][server_type][project_name] = versions
-
- for paper_project in PAPERJARS:
- cache_data["types"]["servers"][paper_project] = self.get_paper_versions(
- paper_project
- )
-
- return cache_data
-
- def manual_refresh_cache(self):
- """
- Manually triggers the cache refresh process.
- """
- if not self._check_sjars_api_alive():
- logger.error("ServerJars API is not available.")
- return False
-
- logger.info("Manual cache refresh requested.")
- cache_data = self._refresh_cache()
-
- # Save the updated cache data
- try:
- with open(self.helper.serverjar_cache, "w", encoding="utf-8") as cache_file:
- json.dump(cache_data, cache_file, indent=4)
- logger.info("Cache file successfully refreshed manually.")
- except Exception as e:
- logger.error(f"Failed to update cache file manually: {e}")
-
- def refresh_cache(self):
- """
- Automatically trigger cache refresh process based age.
-
- This method checks if the cache file is older than a specified number of days
- before deciding to refresh.
- """
- cache_file_path = self.helper.serverjar_cache
-
- # Determine if the cache is old and needs refreshing
- cache_old = self.helper.is_file_older_than_x_days(cache_file_path)
-
- # debug override
- # cache_old = True
-
- if not self._check_sjars_api_alive():
- logger.error("ServerJars API is not available.")
- return False
-
- if not cache_old:
- logger.info("Cache file is not old enough to require automatic refresh.")
- return False
-
- logger.info("Automatic cache refresh initiated due to old cache.")
- cache_data = self._refresh_cache()
-
- # Save the updated cache data
- try:
- with open(cache_file_path, "w", encoding="utf-8") as cache_file:
- json.dump(cache_data, cache_file, indent=4)
- logger.info("Cache file successfully refreshed automatically.")
- except Exception as e:
- logger.error(f"Failed to update cache file automatically: {e}")
-
- def get_fetch_url(self, jar, server, version):
- """
- Constructs the URL for downloading a server JAR file based on the server type.
-
- Supports two main types of server JAR sources:
- - ServerJars API for servers not in PAPERJARS.
- - Paper API for servers available through the Paper project.
-
- Parameters:
- jar (str): Name of the JAR file.
- server (str): Server software name (e.g., "paper").
- version (str): Server version.
-
- Returns:
- str or None: URL for downloading the JAR file, or None if URL cannot be
- constructed or an error occurs.
- """
- try:
- # Check if the server type is not specifically handled by Paper.
- if server not in PAPERJARS:
- return f"{self.base_url}/api/fetchJar/{jar}/{server}/{version}"
-
- # For Paper servers, attempt to get the build for the specified version.
- paper_build_info = self.get_paper_build(server, version)
- if paper_build_info is None:
- # Log an error or handle the case where paper_build_info is None
- logger.error(
- "Error: Unable to get build information for server:"
- f" {server}, version: {version}"
- )
- return None
-
- build = paper_build_info.get("build")
- if not build:
- # Log an error or handle the case where build is None or not found
- logger.error(
- f"Error: Build number not found for server:"
- f" {server}, version: {version}"
- )
- return None
-
- # Construct and return the URL for downloading the Paper server JAR.
- return (
- f"{self.paper_base}/v2/projects/{server}/versions/{version}/"
- f"builds/{build}/downloads/{server}-{version}-{build}.jar"
- )
- except Exception as e:
- logger.error(f"An error occurred while constructing fetch URL: {e}")
- return None
-
- def download_jar(self, jar, server, version, path, server_id):
- update_thread = threading.Thread(
- name=f"server_download-{server_id}-{server}-{version}",
- target=self.a_download_jar,
- daemon=True,
- args=(jar, server, version, path, server_id),
- )
- update_thread.start()
-
- def a_download_jar(self, jar, server, version, path, server_id):
- """
- Downloads a server JAR file and performs post-download actions including
- notifying users and setting import status.
-
- This method waits for the server registration to complete, retrieves the
- download URL for the specified server JAR file.
-
- Upon successful download, it either runs the installer for
- Forge servers or simply finishes the import process for other types. It
- notifies server users about the completion of the download.
-
- Parameters:
- - jar (str): The name of the JAR file to download.
- - server (str): The type of server software (e.g., 'forge', 'paper').
- - version (str): The version of the server software.
- - path (str): The local filesystem path where the JAR file will be saved.
- - server_id (str): The unique identifier for the server being updated or
- imported, used for notifying users and setting the import status.
-
- Returns:
- - bool: True if the JAR file was successfully downloaded and saved;
- False otherwise.
-
- The method ensures that the server is properly registered before proceeding
- with the download and handles exceptions by logging errors and reverting
- the import status if necessary.
- """
- # delaying download for server register to finish
- time.sleep(3)
-
- fetch_url = self.get_fetch_url(jar, server, version)
- if not fetch_url:
- return False
-
- server_users = PermissionsServers.get_server_user_list(server_id)
-
- # Make sure the server is registered before updating its stats
- while True:
- try:
- ServersController.set_import(server_id)
- for user in server_users:
- WebSocketManager().broadcast_user(user, "send_start_reload", {})
- break
- except Exception as ex:
- logger.debug(f"Server not registered yet. Delaying download - {ex}")
-
- # Initiate Download
- jar_dir = os.path.dirname(path)
- jar_name = os.path.basename(path)
- logger.info(fetch_url)
- success = FileHelpers.ssl_get_file(fetch_url, jar_dir, jar_name)
-
- # Post-download actions
- if success:
- if server == "forge":
- # If this is the newer Forge version, run the installer
- ServersController.finish_import(server_id, True)
- else:
- ServersController.finish_import(server_id)
-
- # Notify users
- for user in server_users:
- WebSocketManager().broadcast_user(
- user, "notification", "Executable download finished"
- )
- time.sleep(3) # Delay for user notification
- WebSocketManager().broadcast_user(user, "send_start_reload", {})
- else:
- logger.error(f"Unable to save jar to {path} due to download failure.")
- ServersController.finish_import(server_id)
-
- return success
diff --git a/app/classes/models/crafty_permissions.py b/app/classes/models/crafty_permissions.py
index 7430f332..e7a159d9 100644
--- a/app/classes/models/crafty_permissions.py
+++ b/app/classes/models/crafty_permissions.py
@@ -187,7 +187,7 @@ class PermissionsCrafty:
@staticmethod
def get_api_key_permissions_list(key: ApiKeys):
user = HelperUsers.get_user(key.user_id)
- if user["superuser"] and key.superuser:
+ if user["superuser"] and key.full_access:
return PermissionsCrafty.get_permissions_list()
if user["superuser"]:
# User is superuser but API key isn't
diff --git a/app/classes/models/management.py b/app/classes/models/management.py
index ffe207c2..e59dd39c 100644
--- a/app/classes/models/management.py
+++ b/app/classes/models/management.py
@@ -16,28 +16,10 @@ from app.classes.models.base_model import BaseModel
from app.classes.models.users import HelperUsers
from app.classes.models.servers import Servers
from app.classes.models.server_permissions import PermissionsServers
-from app.classes.shared.main_models import DatabaseShortcuts
from app.classes.shared.websocket_manager import WebSocketManager
logger = logging.getLogger(__name__)
-
-
-# **********************************************************************************
-# Audit_Log Class
-# **********************************************************************************
-class AuditLog(BaseModel):
- audit_id = AutoField()
- created = DateTimeField(default=datetime.datetime.now)
- user_name = CharField(default="")
- user_id = IntegerField(default=0, index=True)
- source_ip = CharField(default="127.0.0.1")
- server_id = ForeignKeyField(
- Servers, backref="audit_server", null=True
- ) # When auditing global events, use server ID null
- log_msg = TextField(default="")
-
- class Meta:
- table_name = "audit_log"
+auth_logger = logging.getLogger("audit_log")
# **********************************************************************************
@@ -149,10 +131,6 @@ class HelpersManagement:
# **********************************************************************************
# Audit_Log Methods
# **********************************************************************************
- @staticmethod
- def get_activity_log():
- query = AuditLog.select()
- return DatabaseShortcuts.return_db_rows(query)
def add_to_audit_log(self, user_id, log_msg, server_id=None, source_ip=None):
logger.debug(f"Adding to audit log User:{user_id} - Message: {log_msg} ")
@@ -166,50 +144,28 @@ class HelpersManagement:
WebSocketManager().broadcast_user(user, "notification", audit_msg)
except Exception as e:
logger.error(f"Error broadcasting to user {user} - {e}")
-
- AuditLog.insert(
- {
- AuditLog.user_name: user_data["username"],
- AuditLog.user_id: user_id,
- AuditLog.server_id: server_id,
- AuditLog.log_msg: audit_msg,
- AuditLog.source_ip: source_ip,
- }
- ).execute()
- # deletes records when there's more than 300
- ordered = AuditLog.select().order_by(+AuditLog.created)
- for item in ordered:
- if not self.helper.get_setting("max_audit_entries"):
- max_entries = 300
- else:
- max_entries = self.helper.get_setting("max_audit_entries")
- if AuditLog.select().count() > max_entries:
- AuditLog.delete().where(AuditLog.audit_id == item.audit_id).execute()
- else:
- return
+ auth_logger.info(
+ str(log_msg),
+ extra={
+ "user_name": user_data["username"],
+ "user_id": user_id,
+ "server_id": server_id,
+ "source_ip": source_ip,
+ },
+ )
def add_to_audit_log_raw(self, user_name, user_id, server_id, log_msg, source_ip):
- AuditLog.insert(
- {
- AuditLog.user_name: user_name,
- AuditLog.user_id: user_id,
- AuditLog.server_id: server_id,
- AuditLog.log_msg: log_msg,
- AuditLog.source_ip: source_ip,
- }
- ).execute()
- # deletes records when there's more than 300
- ordered = AuditLog.select().order_by(+AuditLog.created)
- for item in ordered:
- # configurable through app/config/config.json
- if not self.helper.get_setting("max_audit_entries"):
- max_entries = 300
- else:
- max_entries = self.helper.get_setting("max_audit_entries")
- if AuditLog.select().count() > max_entries:
- AuditLog.delete().where(AuditLog.audit_id == item.audit_id).execute()
- else:
- return
+ if isinstance(server_id, Servers) and server_id is not None:
+ server_id = server_id.server_id
+ auth_logger.info(
+ str(log_msg),
+ extra={
+ "user_name": user_name,
+ "user_id": user_id,
+ "server_id": server_id,
+ "source_ip": source_ip,
+ },
+ )
@staticmethod
def create_crafty_row():
diff --git a/app/classes/models/server_permissions.py b/app/classes/models/server_permissions.py
index 56f9d8ac..12301e30 100644
--- a/app/classes/models/server_permissions.py
+++ b/app/classes/models/server_permissions.py
@@ -264,7 +264,7 @@ class PermissionsServers:
@staticmethod
def get_api_key_permissions_list(key: ApiKeys, server_id: str):
user = HelperUsers.get_user(key.user_id)
- if user["superuser"] and key.superuser:
+ if user["superuser"] and key.full_access:
return PermissionsServers.get_permissions_list()
roles_list = HelperUsers.get_user_roles_id(user["user_id"])
role_server = (
diff --git a/app/classes/models/users.py b/app/classes/models/users.py
index e44d06fb..3f96e651 100644
--- a/app/classes/models/users.py
+++ b/app/classes/models/users.py
@@ -71,7 +71,7 @@ class ApiKeys(BaseModel):
user_id = ForeignKeyField(Users, backref="api_token", index=True)
server_permissions = CharField(default="00000000")
crafty_permissions = CharField(default="000")
- superuser = BooleanField(default=False)
+ full_access = BooleanField(default=False)
class Meta:
table_name = "api_keys"
@@ -408,7 +408,7 @@ class HelperUsers:
def add_user_api_key(
name: str,
user_id: str,
- superuser: bool = False,
+ full_access: bool = False,
server_permissions_mask: t.Optional[str] = None,
crafty_permissions_mask: t.Optional[str] = None,
):
@@ -426,7 +426,7 @@ class HelperUsers:
if crafty_permissions_mask is not None
else {}
),
- ApiKeys.superuser: superuser,
+ ApiKeys.full_access: full_access,
}
).execute()
diff --git a/app/classes/shared/helpers.py b/app/classes/shared/helpers.py
index 0427da11..55a588fc 100644
--- a/app/classes/shared/helpers.py
+++ b/app/classes/shared/helpers.py
@@ -72,7 +72,7 @@ class Helpers:
self.db_path = os.path.join(
self.root_dir, "app", "config", "db", "crafty.sqlite"
)
- self.serverjar_cache = os.path.join(self.config_dir, "serverjars.json")
+ self.big_bucket_cache = os.path.join(self.config_dir, "bigbucket.json")
self.credits_cache = os.path.join(self.config_dir, "credits.json")
self.passhasher = PasswordHasher()
self.exiting = False
@@ -516,6 +516,7 @@ class Helpers:
"monitored_mounts": mounts,
"dir_size_poll_freq_minutes": 5,
"crafty_logs_delete_after_days": 0,
+ "big_bucket_repo": "https://jars.arcadiatech.org",
}
def get_all_settings(self):
diff --git a/app/classes/shared/main_controller.py b/app/classes/shared/main_controller.py
index 9c3219ff..66feff91 100644
--- a/app/classes/shared/main_controller.py
+++ b/app/classes/shared/main_controller.py
@@ -32,7 +32,7 @@ from app.classes.shared.console import Console
from app.classes.shared.helpers import Helpers
from app.classes.shared.file_helpers import FileHelpers
from app.classes.shared.import_helper import ImportHelpers
-from app.classes.minecraft.serverjars import ServerJars
+from app.classes.minecraft.bigbucket import BigBucket
from app.classes.shared.websocket_manager import WebSocketManager
logger = logging.getLogger(__name__)
@@ -43,7 +43,7 @@ class Controller:
self.helper: Helpers = helper
self.file_helper: FileHelpers = file_helper
self.import_helper: ImportHelpers = import_helper
- self.server_jars: ServerJars = ServerJars(helper)
+ self.big_bucket: BigBucket = BigBucket(helper)
self.users_helper: HelperUsers = HelperUsers(database, self.helper)
self.roles_helper: HelperRoles = HelperRoles(database)
self.servers_helper: HelperServers = HelperServers(database)
@@ -436,7 +436,7 @@ class Controller:
if root_create_data["create_type"] == "download_jar":
if Helpers.is_os_windows():
# Let's check for and setup for install server commands
- if create_data["type"] == "forge":
+ if create_data["type"] == "forge-installer":
server_command = (
f"java -Xms{Helpers.float_to_string(min_mem)}M "
f"-Xmx{Helpers.float_to_string(max_mem)}M "
@@ -449,7 +449,7 @@ class Controller:
f'-jar "{server_file}" nogui'
)
else:
- if create_data["type"] == "forge":
+ if create_data["type"] == "forge-installer":
server_command = (
f"java -Xms{Helpers.float_to_string(min_mem)}M "
f"-Xmx{Helpers.float_to_string(max_mem)}M "
@@ -569,19 +569,16 @@ class Controller:
if data["create_type"] == "minecraft_java":
if root_create_data["create_type"] == "download_jar":
# modded update urls from server jars will only update the installer
- if (
- create_data["category"] != "modded"
- and create_data["type"] not in ServerJars.get_paper_jars()
- ):
+ if create_data["type"] != "forge-installer":
server_obj = self.servers.get_server_obj(new_server_id)
- url = (
- "https://api.serverjars.com/api/fetchJar/"
- f"{create_data['category']}"
- f"/{create_data['type']}/{create_data['version']}"
+ url = self.big_bucket.get_fetch_url(
+ create_data["category"],
+ create_data["type"],
+ create_data["version"],
)
server_obj.executable_update_url = url
self.servers.update_server(server_obj)
- self.server_jars.download_jar(
+ self.big_bucket.download_jar(
create_data["category"],
create_data["type"],
create_data["version"],
diff --git a/app/classes/shared/server.py b/app/classes/shared/server.py
index a31cc891..a6c98b89 100644
--- a/app/classes/shared/server.py
+++ b/app/classes/shared/server.py
@@ -690,7 +690,8 @@ class ServerInstance:
try:
# Getting the forge version from the executable command
version = re.findall(
- r"forge-([0-9\.]+)((?:)|(?:-([0-9\.]+)-[a-zA-Z]+)).jar",
+ r"forge-installer-([0-9\.]+)((?:)|"
+ r"(?:-([0-9\.]+)-[a-zA-Z]+)).jar",
server_obj.execution_command,
)
version_param = version[0][0].split(".")
diff --git a/app/classes/shared/tasks.py b/app/classes/shared/tasks.py
index d1b786b9..b9513441 100644
--- a/app/classes/shared/tasks.py
+++ b/app/classes/shared/tasks.py
@@ -685,16 +685,16 @@ class TasksManager:
id="stats",
)
- def serverjar_cache_refresher(self):
- logger.info("Refreshing serverjars.com cache on start")
- self.controller.server_jars.refresh_cache()
+ def big_bucket_cache_refresher(self):
+ logger.info("Refreshing big bucket cache on start")
+ self.controller.big_bucket.refresh_cache()
- logger.info("Scheduling Serverjars.com cache refresh service every 12 hours")
+ logger.info("Scheduling big bucket cache refresh service every 12 hours")
self.scheduler.add_job(
- self.controller.server_jars.refresh_cache,
+ self.controller.big_bucket.refresh_cache,
"interval",
hours=12,
- id="serverjars",
+ id="big_bucket",
)
def realtime(self):
diff --git a/app/classes/web/base_handler.py b/app/classes/web/base_handler.py
index ced6cb97..7cca08e8 100644
--- a/app/classes/web/base_handler.py
+++ b/app/classes/web/base_handler.py
@@ -182,6 +182,7 @@ class BaseHandler(tornado.web.RequestHandler):
t.List[str],
bool,
t.Dict[str, t.Any],
+ str,
]
]:
try:
@@ -190,9 +191,10 @@ class BaseHandler(tornado.web.RequestHandler):
)
superuser = user["superuser"]
+ server_permissions_api_mask = ""
if api_key is not None:
- superuser = superuser and api_key.superuser
-
+ superuser = superuser and api_key.full_access
+ server_permissions_api_mask = api_key.server_permissions
exec_user_role = set()
if superuser:
authorized_servers = self.controller.servers.get_all_defined_servers()
@@ -214,6 +216,7 @@ class BaseHandler(tornado.web.RequestHandler):
user["user_id"]
)
)
+
logger.debug(user["roles"])
for r in user["roles"]:
role = self.controller.roles.get_role(r)
@@ -234,6 +237,7 @@ class BaseHandler(tornado.web.RequestHandler):
exec_user_role,
superuser,
user,
+ server_permissions_api_mask,
)
logging.debug("Auth unsuccessful")
auth_log.error(
diff --git a/app/classes/web/panel_handler.py b/app/classes/web/panel_handler.py
index 716671c8..bbbc9d9e 100644
--- a/app/classes/web/panel_handler.py
+++ b/app/classes/web/panel_handler.py
@@ -168,7 +168,7 @@ class PanelHandler(BaseHandler):
# Commented out because there is no server access control for API keys,
# they just inherit from the host user
# if api_key is not None:
- # superuser = superuser and api_key.superuser
+ # superuser = superuser and api_key.full_access
if server_id is None:
self.redirect("/panel/error?error=Invalid Server ID")
@@ -242,7 +242,7 @@ class PanelHandler(BaseHandler):
api_key, _token_data, exec_user = self.current_user
superuser = exec_user["superuser"]
if api_key is not None:
- superuser = superuser and api_key.superuser
+ superuser = superuser and api_key.full_access
if superuser: # TODO: Figure out a better solution
defined_servers = self.controller.servers.list_defined_servers()
@@ -351,7 +351,7 @@ class PanelHandler(BaseHandler):
"created": api_key.created,
"server_permissions": api_key.server_permissions,
"crafty_permissions": api_key.crafty_permissions,
- "superuser": api_key.superuser,
+ "full_access": api_key.full_access,
}
if api_key is not None
else None
@@ -1237,9 +1237,11 @@ class PanelHandler(BaseHandler):
page_data["schedule"]["interval_type"] = schedule.interval_type
if schedule.interval_type == "reaction":
difficulty = "reaction"
- page_data["parent"] = self.controller.management.get_scheduled_task(
- schedule.parent
- )
+ page_data["parent"] = None
+ if schedule.parent:
+ page_data["parent"] = self.controller.management.get_scheduled_task(
+ schedule.parent
+ )
elif schedule.cron_string == "":
difficulty = "basic"
page_data["parent"] = None
@@ -1356,6 +1358,9 @@ class PanelHandler(BaseHandler):
page_data["crafty_permissions_all"] = (
self.controller.crafty_perms.list_defined_crafty_permissions()
)
+ page_data["user_crafty_permissions"] = (
+ self.controller.crafty_perms.get_crafty_permissions_list(user_id)
+ )
if user_id is None:
self.redirect("/panel/error?error=Invalid User ID")
@@ -1503,8 +1508,6 @@ class PanelHandler(BaseHandler):
template = "panel/panel_edit_role.html"
elif page == "activity_logs":
- page_data["audit_logs"] = self.controller.management.get_activity_log()
-
template = "panel/activity_logs.html"
elif page == "download_file":
diff --git a/app/classes/web/public_handler.py b/app/classes/web/public_handler.py
index 21e2d495..a3d89d25 100644
--- a/app/classes/web/public_handler.py
+++ b/app/classes/web/public_handler.py
@@ -48,7 +48,10 @@ class PublicHandler(BaseHandler):
}
if self.request.query:
- page_data["query"] = self.request.query_arguments.get("next")[0].decode()
+ request_query = self.request.query_arguments.get("next")
+ if not request_query:
+ self.redirect("/login")
+ page_data["query"] = request_query[0].decode()
# sensible defaults
template = "public/404.html"
diff --git a/app/classes/web/routes/api/crafty/announcements/index.py b/app/classes/web/routes/api/crafty/announcements/index.py
index 75f00f16..d66c4473 100644
--- a/app/classes/web/routes/api/crafty/announcements/index.py
+++ b/app/classes/web/routes/api/crafty/announcements/index.py
@@ -26,6 +26,7 @@ class ApiAnnounceIndexHandler(BaseApiHandler):
_,
_,
_user,
+ _,
) = auth_data
data = self.helper.get_announcements()
@@ -72,6 +73,7 @@ class ApiAnnounceIndexHandler(BaseApiHandler):
_,
_,
_user,
+ _,
) = auth_data
try:
data = json.loads(self.request.body)
diff --git a/app/classes/web/routes/api/crafty/clogs/index.py b/app/classes/web/routes/api/crafty/clogs/index.py
index 97a24a34..35f48a7f 100644
--- a/app/classes/web/routes/api/crafty/clogs/index.py
+++ b/app/classes/web/routes/api/crafty/clogs/index.py
@@ -1,3 +1,5 @@
+import os
+import json
from app.classes.web.base_api_handler import BaseApiHandler
@@ -12,6 +14,7 @@ class ApiCraftyLogIndexHandler(BaseApiHandler):
_,
superuser,
_,
+ _,
) = auth_data
if not superuser:
@@ -22,9 +25,17 @@ class ApiCraftyLogIndexHandler(BaseApiHandler):
raise NotImplementedError
if log_type == "audit":
+ with open(
+ os.path.join(self.controller.project_root, "logs", "audit.log"),
+ "r",
+ encoding="utf-8",
+ ) as f:
+ log_lines = [json.loads(line) for line in f]
+ rev_log_lines = log_lines[::-1]
+
return self.finish_json(
200,
- {"status": "ok", "data": self.controller.management.get_activity_log()},
+ {"status": "ok", "data": rev_log_lines},
)
if log_type == "session":
diff --git a/app/classes/web/routes/api/crafty/config/index.py b/app/classes/web/routes/api/crafty/config/index.py
index 40504d76..d625d339 100644
--- a/app/classes/web/routes/api/crafty/config/index.py
+++ b/app/classes/web/routes/api/crafty/config/index.py
@@ -31,6 +31,7 @@ config_json_schema = {
"monitored_mounts": {"type": "array"},
"dir_size_poll_freq_minutes": {"type": "integer"},
"crafty_logs_delete_after_days": {"type": "integer"},
+ "big_bucket_repo": {"type": "string"},
},
"additionalProperties": False,
"minProperties": 1,
@@ -67,6 +68,7 @@ class ApiCraftyConfigIndexHandler(BaseApiHandler):
_,
superuser,
_,
+ _,
) = auth_data
# GET /api/v2/roles?ids=true
@@ -93,13 +95,7 @@ class ApiCraftyConfigIndexHandler(BaseApiHandler):
auth_data = self.authenticate_user()
if not auth_data:
return
- (
- _,
- _,
- _,
- superuser,
- user,
- ) = auth_data
+ (_, _, _, superuser, user, _) = auth_data
if not superuser:
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
@@ -149,6 +145,7 @@ class ApiCraftyCustomizeIndexHandler(BaseApiHandler):
_,
superuser,
_,
+ _,
) = auth_data
# GET /api/v2/roles?ids=true
@@ -181,6 +178,7 @@ class ApiCraftyCustomizeIndexHandler(BaseApiHandler):
_,
superuser,
user,
+ _,
) = auth_data
if not superuser:
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
diff --git a/app/classes/web/routes/api/crafty/config/server_dir.py b/app/classes/web/routes/api/crafty/config/server_dir.py
index 07cf7c26..bc88cba9 100644
--- a/app/classes/web/routes/api/crafty/config/server_dir.py
+++ b/app/classes/web/routes/api/crafty/config/server_dir.py
@@ -24,6 +24,7 @@ class ApiCraftyConfigServerDirHandler(BaseApiHandler):
_,
superuser,
_,
+ _,
) = auth_data
# GET /api/v2/roles?ids=true
@@ -56,6 +57,7 @@ class ApiCraftyConfigServerDirHandler(BaseApiHandler):
_,
_,
_,
+ _,
) = auth_data
if not auth_data:
diff --git a/app/classes/web/routes/api/crafty/exe_cache.py b/app/classes/web/routes/api/crafty/exe_cache.py
index 8836aef8..7fa9743a 100644
--- a/app/classes/web/routes/api/crafty/exe_cache.py
+++ b/app/classes/web/routes/api/crafty/exe_cache.py
@@ -12,16 +12,17 @@ class ApiCraftyJarCacheIndexHandler(BaseApiHandler):
_,
_,
_,
+ _,
) = auth_data
if not auth_data[4]["superuser"]:
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
- self.controller.server_jars.manual_refresh_cache()
+ self.controller.big_bucket.manual_refresh_cache()
self.finish_json(
200,
{
"status": "ok",
- "data": self.controller.server_jars.get_serverjar_data(),
+ "data": self.controller.big_bucket.get_bucket_data(),
},
)
diff --git a/app/classes/web/routes/api/roles/index.py b/app/classes/web/routes/api/roles/index.py
index 0f656dbb..a8612c75 100644
--- a/app/classes/web/routes/api/roles/index.py
+++ b/app/classes/web/routes/api/roles/index.py
@@ -75,6 +75,7 @@ class ApiRolesIndexHandler(BaseApiHandler):
_,
superuser,
_,
+ _,
) = auth_data
# GET /api/v2/roles?ids=true
@@ -107,6 +108,7 @@ class ApiRolesIndexHandler(BaseApiHandler):
_,
superuser,
user,
+ _,
) = auth_data
if not superuser:
diff --git a/app/classes/web/routes/api/roles/role/index.py b/app/classes/web/routes/api/roles/role/index.py
index 97362f5b..73fd9ff3 100644
--- a/app/classes/web/routes/api/roles/role/index.py
+++ b/app/classes/web/routes/api/roles/role/index.py
@@ -74,6 +74,7 @@ class ApiRolesRoleIndexHandler(BaseApiHandler):
_,
superuser,
_,
+ _,
) = auth_data
if not superuser:
@@ -97,6 +98,7 @@ class ApiRolesRoleIndexHandler(BaseApiHandler):
_,
superuser,
user,
+ _,
) = auth_data
if not superuser:
@@ -126,10 +128,19 @@ class ApiRolesRoleIndexHandler(BaseApiHandler):
_,
superuser,
user,
+ _,
) = auth_data
- if not superuser:
- return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
+ role = self.controller.roles.get_role(role_id)
+ if not superuser and user["user_id"] != role["manager"]:
+ return self.finish_json(
+ 400,
+ {
+ "status": "error",
+ "error": "NOT_AUTHORIZED",
+ "error_data": "Not Authorized",
+ },
+ )
try:
data = orjson.loads(self.request.body)
diff --git a/app/classes/web/routes/api/roles/role/servers.py b/app/classes/web/routes/api/roles/role/servers.py
index 0a0eff6f..8f41f6c6 100644
--- a/app/classes/web/routes/api/roles/role/servers.py
+++ b/app/classes/web/routes/api/roles/role/servers.py
@@ -13,6 +13,7 @@ class ApiRolesRoleServersHandler(BaseApiHandler):
_,
superuser,
_,
+ _,
) = auth_data
# GET /api/v2/roles/role/servers?ids=true
diff --git a/app/classes/web/routes/api/roles/role/users.py b/app/classes/web/routes/api/roles/role/users.py
index ac2227ac..48444ead 100644
--- a/app/classes/web/routes/api/roles/role/users.py
+++ b/app/classes/web/routes/api/roles/role/users.py
@@ -12,6 +12,7 @@ class ApiRolesRoleUsersHandler(BaseApiHandler):
_,
superuser,
_,
+ _,
) = auth_data
if not superuser:
diff --git a/app/classes/web/routes/api/servers/index.py b/app/classes/web/routes/api/servers/index.py
index 3c14f604..43cf01e2 100644
--- a/app/classes/web/routes/api/servers/index.py
+++ b/app/classes/web/routes/api/servers/index.py
@@ -139,7 +139,7 @@ new_server_schema = {
"category": {
"title": "Jar Category",
"type": "string",
- "examples": ["modded", "vanilla"],
+ "examples": ["Mc_java_servers", "Mc_java_proxies"],
},
"properties": {
"type": {
@@ -685,6 +685,7 @@ class ApiServersIndexHandler(BaseApiHandler):
_,
_superuser,
user,
+ _,
) = auth_data
if EnumPermissionsCrafty.SERVER_CREATION not in exec_user_crafty_permissions:
diff --git a/app/classes/web/routes/api/servers/server/action.py b/app/classes/web/routes/api/servers/server/action.py
index 526899b5..aba06da3 100644
--- a/app/classes/web/routes/api/servers/server/action.py
+++ b/app/classes/web/routes/api/servers/server/action.py
@@ -18,13 +18,14 @@ class ApiServersServerActionHandler(BaseApiHandler):
if server_id not in [str(x["server_id"]) for x in auth_data[0]]:
# if the user doesn't have access to the server, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
-
- if (
- EnumPermissionsServer.COMMANDS
- not in self.controller.server_perms.get_user_id_permissions_list(
+ mask = self.controller.server_perms.get_lowest_api_perm_mask(
+ self.controller.server_perms.get_user_permissions_mask(
auth_data[4]["user_id"], server_id
- )
- ):
+ ),
+ auth_data[5],
+ )
+ server_permissions = self.controller.server_perms.get_permissions(mask)
+ if EnumPermissionsServer.COMMANDS not in server_permissions:
# if the user doesn't have Commands permission, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
diff --git a/app/classes/web/routes/api/servers/server/backups/backup/index.py b/app/classes/web/routes/api/servers/server/backups/backup/index.py
index 70ceb2b2..1b9ff915 100644
--- a/app/classes/web/routes/api/servers/server/backups/backup/index.py
+++ b/app/classes/web/routes/api/servers/server/backups/backup/index.py
@@ -26,12 +26,14 @@ class ApiServersServerBackupsBackupIndexHandler(BaseApiHandler):
auth_data = self.authenticate_user()
if not auth_data:
return
- if (
- EnumPermissionsServer.BACKUP
- not in self.controller.server_perms.get_user_id_permissions_list(
+ mask = self.controller.server_perms.get_lowest_api_perm_mask(
+ self.controller.server_perms.get_user_permissions_mask(
auth_data[4]["user_id"], server_id
- )
- ):
+ ),
+ auth_data[5],
+ )
+ server_permissions = self.controller.server_perms.get_permissions(mask)
+ if EnumPermissionsServer.BACKUP not in server_permissions:
# if the user doesn't have Schedule permission, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
self.finish_json(200, self.controller.management.get_backup_config(server_id))
@@ -41,12 +43,14 @@ class ApiServersServerBackupsBackupIndexHandler(BaseApiHandler):
backup_conf = self.controller.management.get_backup_config(server_id)
if not auth_data:
return
- if (
- EnumPermissionsServer.BACKUP
- not in self.controller.server_perms.get_user_id_permissions_list(
+ mask = self.controller.server_perms.get_lowest_api_perm_mask(
+ self.controller.server_perms.get_user_permissions_mask(
auth_data[4]["user_id"], server_id
- )
- ):
+ ),
+ auth_data[5],
+ )
+ server_permissions = self.controller.server_perms.get_permissions(mask)
+ if EnumPermissionsServer.BACKUP not in server_permissions:
# if the user doesn't have Schedule permission, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
@@ -89,12 +93,14 @@ class ApiServersServerBackupsBackupIndexHandler(BaseApiHandler):
auth_data = self.authenticate_user()
if not auth_data:
return
- if (
- EnumPermissionsServer.BACKUP
- not in self.controller.server_perms.get_user_id_permissions_list(
+ mask = self.controller.server_perms.get_lowest_api_perm_mask(
+ self.controller.server_perms.get_user_permissions_mask(
auth_data[4]["user_id"], server_id
- )
- ):
+ ),
+ auth_data[5],
+ )
+ server_permissions = self.controller.server_perms.get_permissions(mask)
+ if EnumPermissionsServer.BACKUP not in server_permissions:
# if the user doesn't have Schedule permission, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
diff --git a/app/classes/web/routes/api/servers/server/backups/index.py b/app/classes/web/routes/api/servers/server/backups/index.py
index 9e47bcfc..865fe25a 100644
--- a/app/classes/web/routes/api/servers/server/backups/index.py
+++ b/app/classes/web/routes/api/servers/server/backups/index.py
@@ -42,12 +42,14 @@ class ApiServersServerBackupsIndexHandler(BaseApiHandler):
auth_data = self.authenticate_user()
if not auth_data:
return
- if (
- EnumPermissionsServer.BACKUP
- not in self.controller.server_perms.get_user_id_permissions_list(
+ mask = self.controller.server_perms.get_lowest_api_perm_mask(
+ self.controller.server_perms.get_user_permissions_mask(
auth_data[4]["user_id"], server_id
- )
- ):
+ ),
+ auth_data[5],
+ )
+ server_permissions = self.controller.server_perms.get_permissions(mask)
+ if EnumPermissionsServer.BACKUP not in server_permissions:
# if the user doesn't have Schedule permission, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
self.finish_json(200, self.controller.management.get_backup_config(server_id))
@@ -82,13 +84,14 @@ class ApiServersServerBackupsIndexHandler(BaseApiHandler):
if server_id not in [str(x["server_id"]) for x in auth_data[0]]:
# if the user doesn't have access to the server, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
-
- if (
- EnumPermissionsServer.BACKUP
- not in self.controller.server_perms.get_user_id_permissions_list(
+ mask = self.controller.server_perms.get_lowest_api_perm_mask(
+ self.controller.server_perms.get_user_permissions_mask(
auth_data[4]["user_id"], server_id
- )
- ):
+ ),
+ auth_data[5],
+ )
+ server_permissions = self.controller.server_perms.get_permissions(mask)
+ if EnumPermissionsServer.BACKUP not in server_permissions:
# if the user doesn't have Schedule permission, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
diff --git a/app/classes/web/routes/api/servers/server/files.py b/app/classes/web/routes/api/servers/server/files.py
index 8e70d4fe..2951ff25 100644
--- a/app/classes/web/routes/api/servers/server/files.py
+++ b/app/classes/web/routes/api/servers/server/files.py
@@ -80,16 +80,16 @@ class ApiServersServerFilesIndexHandler(BaseApiHandler):
if server_id not in [str(x["server_id"]) for x in auth_data[0]]:
# if the user doesn't have access to the server, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
-
+ mask = self.controller.server_perms.get_lowest_api_perm_mask(
+ self.controller.server_perms.get_user_permissions_mask(
+ auth_data[4]["user_id"], server_id
+ ),
+ auth_data[5],
+ )
+ server_permissions = self.controller.server_perms.get_permissions(mask)
if (
- EnumPermissionsServer.FILES
- not in self.controller.server_perms.get_user_id_permissions_list(
- auth_data[4]["user_id"], server_id
- )
- and EnumPermissionsServer.BACKUP
- not in self.controller.server_perms.get_user_id_permissions_list(
- auth_data[4]["user_id"], server_id
- )
+ EnumPermissionsServer.FILES not in server_permissions
+ and EnumPermissionsServer.BACKUP not in server_permissions
):
# if the user doesn't have Files or Backup permission, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
@@ -197,13 +197,14 @@ class ApiServersServerFilesIndexHandler(BaseApiHandler):
if server_id not in [str(x["server_id"]) for x in auth_data[0]]:
# if the user doesn't have access to the server, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
-
- if (
- EnumPermissionsServer.FILES
- not in self.controller.server_perms.get_user_id_permissions_list(
+ mask = self.controller.server_perms.get_lowest_api_perm_mask(
+ self.controller.server_perms.get_user_permissions_mask(
auth_data[4]["user_id"], server_id
- )
- ):
+ ),
+ auth_data[5],
+ )
+ server_permissions = self.controller.server_perms.get_permissions(mask)
+ if EnumPermissionsServer.FILES not in server_permissions:
# if the user doesn't have Files permission, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
try:
@@ -254,13 +255,14 @@ class ApiServersServerFilesIndexHandler(BaseApiHandler):
if server_id not in [str(x["server_id"]) for x in auth_data[0]]:
# if the user doesn't have access to the server, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
-
- if (
- EnumPermissionsServer.FILES
- not in self.controller.server_perms.get_user_id_permissions_list(
+ mask = self.controller.server_perms.get_lowest_api_perm_mask(
+ self.controller.server_perms.get_user_permissions_mask(
auth_data[4]["user_id"], server_id
- )
- ):
+ ),
+ auth_data[5],
+ )
+ server_permissions = self.controller.server_perms.get_permissions(mask)
+ if EnumPermissionsServer.FILES not in server_permissions:
# if the user doesn't have Files permission, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
try:
@@ -307,13 +309,14 @@ class ApiServersServerFilesIndexHandler(BaseApiHandler):
if server_id not in [str(x["server_id"]) for x in auth_data[0]]:
# if the user doesn't have access to the server, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
-
- if (
- EnumPermissionsServer.FILES
- not in self.controller.server_perms.get_user_id_permissions_list(
+ mask = self.controller.server_perms.get_lowest_api_perm_mask(
+ self.controller.server_perms.get_user_permissions_mask(
auth_data[4]["user_id"], server_id
- )
- ):
+ ),
+ auth_data[5],
+ )
+ server_permissions = self.controller.server_perms.get_permissions(mask)
+ if EnumPermissionsServer.FILES not in server_permissions:
# if the user doesn't have Files permission, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
try:
@@ -373,13 +376,14 @@ class ApiServersServerFilesCreateHandler(BaseApiHandler):
if server_id not in [str(x["server_id"]) for x in auth_data[0]]:
# if the user doesn't have access to the server, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
-
- if (
- EnumPermissionsServer.FILES
- not in self.controller.server_perms.get_user_id_permissions_list(
+ mask = self.controller.server_perms.get_lowest_api_perm_mask(
+ self.controller.server_perms.get_user_permissions_mask(
auth_data[4]["user_id"], server_id
- )
- ):
+ ),
+ auth_data[5],
+ )
+ server_permissions = self.controller.server_perms.get_permissions(mask)
+ if EnumPermissionsServer.FILES not in server_permissions:
# if the user doesn't have Files permission, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
try:
@@ -438,13 +442,14 @@ class ApiServersServerFilesCreateHandler(BaseApiHandler):
if server_id not in [str(x["server_id"]) for x in auth_data[0]]:
# if the user doesn't have access to the server, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
-
- if (
- EnumPermissionsServer.FILES
- not in self.controller.server_perms.get_user_id_permissions_list(
+ mask = self.controller.server_perms.get_lowest_api_perm_mask(
+ self.controller.server_perms.get_user_permissions_mask(
auth_data[4]["user_id"], server_id
- )
- ):
+ ),
+ auth_data[5],
+ )
+ server_permissions = self.controller.server_perms.get_permissions(mask)
+ if EnumPermissionsServer.FILES not in server_permissions:
# if the user doesn't have Files permission, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
try:
@@ -504,13 +509,14 @@ class ApiServersServerFilesZipHandler(BaseApiHandler):
if server_id not in [str(x["server_id"]) for x in auth_data[0]]:
# if the user doesn't have access to the server, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
-
- if (
- EnumPermissionsServer.FILES
- not in self.controller.server_perms.get_user_id_permissions_list(
+ mask = self.controller.server_perms.get_lowest_api_perm_mask(
+ self.controller.server_perms.get_user_permissions_mask(
auth_data[4]["user_id"], server_id
- )
- ):
+ ),
+ auth_data[5],
+ )
+ server_permissions = self.controller.server_perms.get_permissions(mask)
+ if EnumPermissionsServer.FILES not in server_permissions:
# if the user doesn't have Files permission, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
try:
diff --git a/app/classes/web/routes/api/servers/server/index.py b/app/classes/web/routes/api/servers/server/index.py
index 81035bd0..9bfc3a9a 100644
--- a/app/classes/web/routes/api/servers/server/index.py
+++ b/app/classes/web/routes/api/servers/server/index.py
@@ -102,13 +102,14 @@ class ApiServersServerIndexHandler(BaseApiHandler):
if server_id not in [str(x["server_id"]) for x in auth_data[0]]:
# if the user doesn't have access to the server, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
-
- if (
- EnumPermissionsServer.CONFIG
- not in self.controller.server_perms.get_user_id_permissions_list(
+ mask = self.controller.server_perms.get_lowest_api_perm_mask(
+ self.controller.server_perms.get_user_permissions_mask(
auth_data[4]["user_id"], server_id
- )
- ):
+ ),
+ auth_data[5],
+ )
+ server_permissions = self.controller.server_perms.get_permissions(mask)
+ if EnumPermissionsServer.CONFIG not in server_permissions:
# if the user doesn't have Config permission, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
@@ -154,13 +155,14 @@ class ApiServersServerIndexHandler(BaseApiHandler):
if server_id not in [str(x["server_id"]) for x in auth_data[0]]:
# if the user doesn't have access to the server, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
-
- if (
- EnumPermissionsServer.CONFIG
- not in self.controller.server_perms.get_user_id_permissions_list(
+ mask = self.controller.server_perms.get_lowest_api_perm_mask(
+ self.controller.server_perms.get_user_permissions_mask(
auth_data[4]["user_id"], server_id
- )
- ):
+ ),
+ auth_data[5],
+ )
+ server_permissions = self.controller.server_perms.get_permissions(mask)
+ if EnumPermissionsServer.CONFIG not in server_permissions:
# if the user doesn't have Config permission, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
diff --git a/app/classes/web/routes/api/servers/server/logs.py b/app/classes/web/routes/api/servers/server/logs.py
index 94a8a71b..eb6ede00 100644
--- a/app/classes/web/routes/api/servers/server/logs.py
+++ b/app/classes/web/routes/api/servers/server/logs.py
@@ -30,13 +30,14 @@ class ApiServersServerLogsHandler(BaseApiHandler):
if server_id not in [str(x["server_id"]) for x in auth_data[0]]:
# if the user doesn't have access to the server, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
-
- if (
- EnumPermissionsServer.LOGS
- not in self.controller.server_perms.get_user_id_permissions_list(
+ mask = self.controller.server_perms.get_lowest_api_perm_mask(
+ self.controller.server_perms.get_user_permissions_mask(
auth_data[4]["user_id"], server_id
- )
- ):
+ ),
+ auth_data[5],
+ )
+ server_permissions = self.controller.server_perms.get_permissions(mask)
+ if EnumPermissionsServer.LOGS not in server_permissions:
# if the user doesn't have Logs permission, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
diff --git a/app/classes/web/routes/api/servers/server/stdin.py b/app/classes/web/routes/api/servers/server/stdin.py
index ba8400b7..ca2cd7d9 100644
--- a/app/classes/web/routes/api/servers/server/stdin.py
+++ b/app/classes/web/routes/api/servers/server/stdin.py
@@ -16,13 +16,14 @@ class ApiServersServerStdinHandler(BaseApiHandler):
if server_id not in [str(x["server_id"]) for x in auth_data[0]]:
# if the user doesn't have access to the server, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
-
- if (
- EnumPermissionsServer.COMMANDS
- not in self.controller.server_perms.get_user_id_permissions_list(
+ mask = self.controller.server_perms.get_lowest_api_perm_mask(
+ self.controller.server_perms.get_user_permissions_mask(
auth_data[4]["user_id"], server_id
- )
- ):
+ ),
+ auth_data[5],
+ )
+ server_permissions = self.controller.server_perms.get_permissions(mask)
+ if EnumPermissionsServer.COMMANDS not in server_permissions:
# if the user doesn't have Commands permission, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
diff --git a/app/classes/web/routes/api/servers/server/tasks/index.py b/app/classes/web/routes/api/servers/server/tasks/index.py
index 8e98bbbe..0c03319c 100644
--- a/app/classes/web/routes/api/servers/server/tasks/index.py
+++ b/app/classes/web/routes/api/servers/server/tasks/index.py
@@ -78,13 +78,14 @@ class ApiServersServerTasksIndexHandler(BaseApiHandler):
if server_id not in [str(x["server_id"]) for x in auth_data[0]]:
# if the user doesn't have access to the server, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
-
- if (
- EnumPermissionsServer.SCHEDULE
- not in self.controller.server_perms.get_user_id_permissions_list(
+ mask = self.controller.server_perms.get_lowest_api_perm_mask(
+ self.controller.server_perms.get_user_permissions_mask(
auth_data[4]["user_id"], server_id
- )
- ):
+ ),
+ auth_data[5],
+ )
+ server_permissions = self.controller.server_perms.get_permissions(mask)
+ if EnumPermissionsServer.SCHEDULE not in server_permissions:
# if the user doesn't have Schedule permission, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
data["server_id"] = server_id
diff --git a/app/classes/web/routes/api/servers/server/tasks/task/index.py b/app/classes/web/routes/api/servers/server/tasks/task/index.py
index 742312a6..dac60762 100644
--- a/app/classes/web/routes/api/servers/server/tasks/task/index.py
+++ b/app/classes/web/routes/api/servers/server/tasks/task/index.py
@@ -54,12 +54,14 @@ class ApiServersServerTasksTaskIndexHandler(BaseApiHandler):
auth_data = self.authenticate_user()
if not auth_data:
return
- if (
- EnumPermissionsServer.SCHEDULE
- not in self.controller.server_perms.get_user_id_permissions_list(
+ mask = self.controller.server_perms.get_lowest_api_perm_mask(
+ self.controller.server_perms.get_user_permissions_mask(
auth_data[4]["user_id"], server_id
- )
- ):
+ ),
+ auth_data[5],
+ )
+ server_permissions = self.controller.server_perms.get_permissions(mask)
+ if EnumPermissionsServer.SCHEDULE not in server_permissions:
# if the user doesn't have Schedule permission, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
self.finish_json(200, self.controller.management.get_scheduled_task(task_id))
@@ -68,12 +70,14 @@ class ApiServersServerTasksTaskIndexHandler(BaseApiHandler):
auth_data = self.authenticate_user()
if not auth_data:
return
- if (
- EnumPermissionsServer.SCHEDULE
- not in self.controller.server_perms.get_user_id_permissions_list(
+ mask = self.controller.server_perms.get_lowest_api_perm_mask(
+ self.controller.server_perms.get_user_permissions_mask(
auth_data[4]["user_id"], server_id
- )
- ):
+ ),
+ auth_data[5],
+ )
+ server_permissions = self.controller.server_perms.get_permissions(mask)
+ if EnumPermissionsServer.SCHEDULE not in server_permissions:
# if the user doesn't have Schedule permission, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
@@ -120,13 +124,14 @@ class ApiServersServerTasksTaskIndexHandler(BaseApiHandler):
if server_id not in [str(x["server_id"]) for x in auth_data[0]]:
# if the user doesn't have access to the server, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
-
- if (
- EnumPermissionsServer.SCHEDULE
- not in self.controller.server_perms.get_user_id_permissions_list(
+ mask = self.controller.server_perms.get_lowest_api_perm_mask(
+ self.controller.server_perms.get_user_permissions_mask(
auth_data[4]["user_id"], server_id
- )
- ):
+ ),
+ auth_data[5],
+ )
+ server_permissions = self.controller.server_perms.get_permissions(mask)
+ if EnumPermissionsServer.SCHEDULE not in server_permissions:
# if the user doesn't have Schedule permission, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
diff --git a/app/classes/web/routes/api/servers/server/webhooks/index.py b/app/classes/web/routes/api/servers/server/webhooks/index.py
index 223171c8..2557c309 100644
--- a/app/classes/web/routes/api/servers/server/webhooks/index.py
+++ b/app/classes/web/routes/api/servers/server/webhooks/index.py
@@ -38,12 +38,14 @@ class ApiServersServerWebhooksIndexHandler(BaseApiHandler):
auth_data = self.authenticate_user()
if not auth_data:
return
- if (
- EnumPermissionsServer.CONFIG
- not in self.controller.server_perms.get_user_id_permissions_list(
+ mask = self.controller.server_perms.get_lowest_api_perm_mask(
+ self.controller.server_perms.get_user_permissions_mask(
auth_data[4]["user_id"], server_id
- )
- ):
+ ),
+ auth_data[5],
+ )
+ server_permissions = self.controller.server_perms.get_permissions(mask)
+ if EnumPermissionsServer.CONFIG not in server_permissions:
# if the user doesn't have Schedule permission, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
self.finish_json(
@@ -81,13 +83,14 @@ class ApiServersServerWebhooksIndexHandler(BaseApiHandler):
if server_id not in [str(x["server_id"]) for x in auth_data[0]]:
# if the user doesn't have access to the server, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
-
- if (
- EnumPermissionsServer.CONFIG
- not in self.controller.server_perms.get_user_id_permissions_list(
+ mask = self.controller.server_perms.get_lowest_api_perm_mask(
+ self.controller.server_perms.get_user_permissions_mask(
auth_data[4]["user_id"], server_id
- )
- ):
+ ),
+ auth_data[5],
+ )
+ server_permissions = self.controller.server_perms.get_permissions(mask)
+ if EnumPermissionsServer.CONFIG not in server_permissions:
# if the user doesn't have Schedule permission, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
data["server_id"] = server_id
diff --git a/app/classes/web/routes/api/servers/server/webhooks/webhook/index.py b/app/classes/web/routes/api/servers/server/webhooks/webhook/index.py
index 4b58011e..c94aa975 100644
--- a/app/classes/web/routes/api/servers/server/webhooks/webhook/index.py
+++ b/app/classes/web/routes/api/servers/server/webhooks/webhook/index.py
@@ -39,12 +39,14 @@ class ApiServersServerWebhooksManagementIndexHandler(BaseApiHandler):
auth_data = self.authenticate_user()
if not auth_data:
return
- if (
- EnumPermissionsServer.CONFIG
- not in self.controller.server_perms.get_user_id_permissions_list(
+ mask = self.controller.server_perms.get_lowest_api_perm_mask(
+ self.controller.server_perms.get_user_permissions_mask(
auth_data[4]["user_id"], server_id
- )
- ):
+ ),
+ auth_data[5],
+ )
+ server_permissions = self.controller.server_perms.get_permissions(mask)
+ if EnumPermissionsServer.CONFIG not in server_permissions:
# if the user doesn't have Schedule permission, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
if (
@@ -66,12 +68,14 @@ class ApiServersServerWebhooksManagementIndexHandler(BaseApiHandler):
auth_data = self.authenticate_user()
if not auth_data:
return
- if (
- EnumPermissionsServer.CONFIG
- not in self.controller.server_perms.get_user_id_permissions_list(
+ mask = self.controller.server_perms.get_lowest_api_perm_mask(
+ self.controller.server_perms.get_user_permissions_mask(
auth_data[4]["user_id"], server_id
- )
- ):
+ ),
+ auth_data[5],
+ )
+ server_permissions = self.controller.server_perms.get_permissions(mask)
+ if EnumPermissionsServer.CONFIG not in server_permissions:
# if the user doesn't have Schedule permission, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
@@ -117,13 +121,14 @@ class ApiServersServerWebhooksManagementIndexHandler(BaseApiHandler):
if server_id not in [str(x["server_id"]) for x in auth_data[0]]:
# if the user doesn't have access to the server, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
-
- if (
- EnumPermissionsServer.CONFIG
- not in self.controller.server_perms.get_user_id_permissions_list(
+ mask = self.controller.server_perms.get_lowest_api_perm_mask(
+ self.controller.server_perms.get_user_permissions_mask(
auth_data[4]["user_id"], server_id
- )
- ):
+ ),
+ auth_data[5],
+ )
+ server_permissions = self.controller.server_perms.get_permissions(mask)
+ if EnumPermissionsServer.CONFIG not in server_permissions:
# if the user doesn't have Schedule permission, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
@@ -159,13 +164,14 @@ class ApiServersServerWebhooksManagementIndexHandler(BaseApiHandler):
if server_id not in [str(x["server_id"]) for x in auth_data[0]]:
# if the user doesn't have access to the server, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
-
- if (
- EnumPermissionsServer.CONFIG
- not in self.controller.server_perms.get_user_id_permissions_list(
+ mask = self.controller.server_perms.get_lowest_api_perm_mask(
+ self.controller.server_perms.get_user_permissions_mask(
auth_data[4]["user_id"], server_id
- )
- ):
+ ),
+ auth_data[5],
+ )
+ server_permissions = self.controller.server_perms.get_permissions(mask)
+ if EnumPermissionsServer.CONFIG not in server_permissions:
# if the user doesn't have Schedule permission, return an error
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
webhook = self.controller.management.get_webhook_by_id(webhook_id)
diff --git a/app/classes/web/routes/api/users/index.py b/app/classes/web/routes/api/users/index.py
index fef154a0..dbdb1ac0 100644
--- a/app/classes/web/routes/api/users/index.py
+++ b/app/classes/web/routes/api/users/index.py
@@ -21,6 +21,7 @@ class ApiUsersIndexHandler(BaseApiHandler):
_,
_,
user,
+ _,
) = auth_data
# GET /api/v2/users?ids=true
@@ -70,6 +71,7 @@ class ApiUsersIndexHandler(BaseApiHandler):
_,
superuser,
user,
+ _,
) = auth_data
if EnumPermissionsCrafty.USER_CONFIG not in exec_user_crafty_permissions:
@@ -149,11 +151,12 @@ class ApiUsersIndexHandler(BaseApiHandler):
400, {"status": "error", "error": "INVALID_SUPERUSER_CREATE"}
)
- if len(roles) != 0 and not superuser:
- # HACK: This should check if the user has the roles or something
- return self.finish_json(
- 400, {"status": "error", "error": "INVALID_ROLES_CREATE"}
- )
+ for role in roles:
+ role = self.controller.roles.get_role(role)
+ if int(role["manager"]) != int(auth_data[4]["user_id"]) and not superuser:
+ return self.finish_json(
+ 400, {"status": "error", "error": "INVALID_ROLES_CREATE"}
+ )
# TODO: do this in the most efficient way
user_id = self.controller.users.add_user(
diff --git a/app/classes/web/routes/api/users/user/api.py b/app/classes/web/routes/api/users/user/api.py
index 9bdafadf..3891ef83 100644
--- a/app/classes/web/routes/api/users/user/api.py
+++ b/app/classes/web/routes/api/users/user/api.py
@@ -75,7 +75,7 @@ class ApiUsersUserKeyHandler(BaseApiHandler):
"name": key.name,
"server_permissions": key.server_permissions,
"crafty_permissions": key.crafty_permissions,
- "superuser": key.superuser,
+ "full_access": key.full_access,
}
)
self.finish_json(
@@ -99,7 +99,7 @@ class ApiUsersUserKeyHandler(BaseApiHandler):
"type": "string",
"pattern": "^[01]{3}$", # 8 bits, see EnumPermissionsCrafty
},
- "superuser": {"type": "boolean"},
+ "full_access": {"type": "boolean"},
},
"additionalProperties": False,
"minProperties": 1,
@@ -113,6 +113,7 @@ class ApiUsersUserKeyHandler(BaseApiHandler):
_,
_superuser,
user,
+ _,
) = auth_data
try:
@@ -163,7 +164,7 @@ class ApiUsersUserKeyHandler(BaseApiHandler):
key_id = self.controller.users.add_user_api_key(
data["name"],
user_id,
- data["superuser"],
+ data["full_access"],
data["server_permissions_mask"],
data["crafty_permissions_mask"],
)
@@ -188,6 +189,7 @@ class ApiUsersUserKeyHandler(BaseApiHandler):
_,
_,
_user,
+ _,
) = auth_data
if key_id:
key = self.controller.users.get_user_api_key(key_id)
diff --git a/app/classes/web/routes/api/users/user/index.py b/app/classes/web/routes/api/users/user/index.py
index 6efee93e..9fa46200 100644
--- a/app/classes/web/routes/api/users/user/index.py
+++ b/app/classes/web/routes/api/users/user/index.py
@@ -24,6 +24,7 @@ class ApiUsersUserIndexHandler(BaseApiHandler):
_,
_,
user,
+ _,
) = auth_data
if user_id in ["@me", user["user_id"]]:
@@ -72,6 +73,7 @@ class ApiUsersUserIndexHandler(BaseApiHandler):
_,
_,
user,
+ _,
) = auth_data
if (user_id in ["@me", user["user_id"]]) and self.helper.get_setting(
@@ -121,6 +123,7 @@ class ApiUsersUserIndexHandler(BaseApiHandler):
_,
superuser,
user,
+ _,
) = auth_data
try:
diff --git a/app/classes/web/routes/api/users/user/permissions.py b/app/classes/web/routes/api/users/user/permissions.py
index 5981eaf4..d0f496f2 100644
--- a/app/classes/web/routes/api/users/user/permissions.py
+++ b/app/classes/web/routes/api/users/user/permissions.py
@@ -27,6 +27,7 @@ class ApiUsersUserPermissionsHandler(BaseApiHandler):
_,
_,
user,
+ _,
) = auth_data
if user_id in ["@me", user["user_id"]]:
diff --git a/app/classes/web/routes/api/users/user/public.py b/app/classes/web/routes/api/users/user/public.py
index b67ab61e..e016babc 100644
--- a/app/classes/web/routes/api/users/user/public.py
+++ b/app/classes/web/routes/api/users/user/public.py
@@ -17,6 +17,7 @@ class ApiUsersUserPublicHandler(BaseApiHandler):
_,
_,
user,
+ _,
) = auth_data
if user_id == "@me":
diff --git a/app/classes/web/server_handler.py b/app/classes/web/server_handler.py
index 62b76f3c..a8e278b2 100644
--- a/app/classes/web/server_handler.py
+++ b/app/classes/web/server_handler.py
@@ -30,7 +30,7 @@ class ServerHandler(BaseHandler):
) = self.current_user
superuser = exec_user["superuser"]
if api_key is not None:
- superuser = superuser and api_key.superuser
+ superuser = superuser and api_key.full_access
if superuser:
defined_servers = self.controller.servers.list_defined_servers()
@@ -124,7 +124,7 @@ class ServerHandler(BaseHandler):
"created": api_key.created,
"server_permissions": api_key.server_permissions,
"crafty_permissions": api_key.crafty_permissions,
- "superuser": api_key.superuser,
+ "full_access": api_key.full_access,
}
if api_key is not None
else None
@@ -146,12 +146,12 @@ class ServerHandler(BaseHandler):
return
page_data["server_api"] = False
if page_data["online"]:
- page_data["server_api"] = self.helper.check_address_status(
- "https://api.serverjars.com"
+ page_data["server_api"] = (
+ self.controller.big_bucket._check_bucket_alive()
)
- page_data["server_types"] = self.controller.server_jars.get_serverjar_data()
+ page_data["server_types"] = self.controller.big_bucket.get_bucket_data()
page_data["js_server_types"] = json.dumps(
- self.controller.server_jars.get_serverjar_data()
+ self.controller.big_bucket.get_bucket_data()
)
if page_data["server_types"] is None:
page_data["server_types"] = []
diff --git a/app/classes/web/upload_handler.py b/app/classes/web/upload_handler.py
index 0667dd12..747fa63b 100644
--- a/app/classes/web/upload_handler.py
+++ b/app/classes/web/upload_handler.py
@@ -42,7 +42,7 @@ class UploadHandler(BaseHandler):
if self.upload_type == "server_import":
superuser = exec_user["superuser"]
if api_key is not None:
- superuser = superuser and api_key.superuser
+ superuser = superuser and api_key.full_access
user_id = exec_user["user_id"]
stream_size_value = self.helper.get_setting("stream_size_GB")
@@ -133,7 +133,7 @@ class UploadHandler(BaseHandler):
elif self.upload_type == "background":
superuser = exec_user["superuser"]
if api_key is not None:
- superuser = superuser and api_key.superuser
+ superuser = superuser and api_key.full_access
user_id = exec_user["user_id"]
stream_size_value = self.helper.get_setting("stream_size_GB")
@@ -212,7 +212,7 @@ class UploadHandler(BaseHandler):
server_id = self.get_argument("server_id", None)
superuser = exec_user["superuser"]
if api_key is not None:
- superuser = superuser and api_key.superuser
+ superuser = superuser and api_key.full_access
user_id = exec_user["user_id"]
stream_size_value = self.helper.get_setting("stream_size_GB")
diff --git a/app/config/logging.json b/app/config/logging.json
index fd1173eb..d0a20cdf 100644
--- a/app/config/logging.json
+++ b/app/config/logging.json
@@ -14,6 +14,9 @@
"auth": {
"format": "%(asctime)s - [AUTH] - %(levelname)s - %(message)s"
},
+ "audit": {
+ "()": "app.classes.logging.log_formatter.JsonFormatter"
+ },
"cmd_queue": {
"format": "%(asctime)s - [CMD_QUEUE] - %(levelname)s - %(message)s"
}
@@ -70,6 +73,14 @@
"maxBytes": 10485760,
"backupCount": 20,
"encoding": "utf8"
+ },
+ "audit_log_handler": {
+ "class": "logging.handlers.RotatingFileHandler",
+ "formatter": "audit",
+ "filename": "logs/audit.log",
+ "maxBytes": 10485760,
+ "backupCount": 20,
+ "encoding": "utf8"
}
},
"loggers": {
@@ -108,6 +119,12 @@
"cmd_queue_file_handler"
],
"propagate": false
+ },
+ "audit_log": {
+ "level": "INFO",
+ "handlers": [
+ "audit_log_handler"
+ ]
}
}
}
\ No newline at end of file
diff --git a/app/config/version.json b/app/config/version.json
index 8dffa727..53c90a03 100644
--- a/app/config/version.json
+++ b/app/config/version.json
@@ -1,5 +1,5 @@
{
"major": 4,
- "minor": 3,
- "sub": 2
+ "minor": 4,
+ "sub": 0
}
diff --git a/app/frontend/static/assets/images/serverjars/FULL-WHITE.svg b/app/frontend/static/assets/images/serverjars/FULL-WHITE.svg
deleted file mode 100644
index d5036723..00000000
--- a/app/frontend/static/assets/images/serverjars/FULL-WHITE.svg
+++ /dev/null
@@ -1,120 +0,0 @@
-
-
-
diff --git a/app/frontend/static/assets/images/serverjars/ICON.svg b/app/frontend/static/assets/images/serverjars/ICON.svg
deleted file mode 100644
index 2adc4cff..00000000
--- a/app/frontend/static/assets/images/serverjars/ICON.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/app/frontend/templates/panel/activity_logs.html b/app/frontend/templates/panel/activity_logs.html
index 389edaf5..e053e50e 100644
--- a/app/frontend/templates/panel/activity_logs.html
+++ b/app/frontend/templates/panel/activity_logs.html
@@ -36,25 +36,21 @@
Username | -Time | -Action | -Server ID | -IP | +Time | +Username | +Action | +Server ID | +IP |
---|---|---|---|---|---|---|---|---|---|
{{ row['user_name'] }} | -- {{ row['created'].strftime('%Y-%m-%d %H:%M:%S') }} + |
+ {{ translate('datatables', + 'loadingRecords', data['lang'])}} |
- {{ row['log_msg'] }} | -{{ row['server_id'] }} | -{{ row['source_ip'] }} |
{{ - translate('error', 'serverJars1', data['lang']) }} {{ translate('error', + translate('error', 'bigBucket1', data['lang']) }} {{ + translate('error', 'craftyStatus', data['lang']) }} - {{ translate('error', 'serverJars2', data['lang']) }}
+ {{ translate('error', 'bigBucket2', data['lang']) }}{{ translate('error', 'selfHost', + data['lang'])}}Please wait while we gather your files...
', closeButton: false }); - setTimeout(function(){ + setTimeout(function () { getDirView(); }, 2000); } else { @@ -845,9 +846,9 @@ message: 'Please wait while we gather your files...
', closeButton: false }); - setTimeout(function(){ - getDirView(); - }, 2000); + setTimeout(function () { + getDirView(); + }, 2000); }); var upload = false; var file; @@ -1137,7 +1138,7 @@ function wait_msg(importing) { bootbox.alert({ title: importing ? '{% raw translate("serverWizard", "importing", data["lang"]) %}' : '{% raw translate("serverWizard", "downloading", data["lang"]) %}', - message: importing ? ' {% raw translate("serverWizard", "bePatient", data["lang"]) %}' : ' {% raw translate("serverWizard", "bePatient", data["lang"]) %}