Merge branch 'dev' into 'master'

4.0.1 Patches,

See merge request crafty-controller/crafty-4!343
This commit is contained in:
Iain Powrie 2022-06-15 04:19:30 +00:00
commit a8b9e92c18
23 changed files with 305 additions and 355 deletions

View File

@ -97,9 +97,6 @@ disable=abstract-method,
logging-fstring-interpolation,
logging-not-lazy,
missing-docstring,
no-else-break,
no-else-continue,
no-else-return,
no-value-for-parameter,
not-an-iterable,
protected-access,

View File

@ -2,11 +2,11 @@
[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
[![Supported Python Versions](https://shields.io/badge/python-3.8%20%7C%203.9%20%7C%203.10%20-blue)](https://www.python.org)
[![Version(temp-hardcoded)](https://img.shields.io/badge/release-v4.0.0--beta-orange)](https://gitlab.com/crafty-controller/crafty-4)
[![Version(temp-hardcoded)](https://img.shields.io/badge/release-v4.0.1--beta-orange)](https://gitlab.com/crafty-controller/crafty-4)
[![Code Quality(temp-hardcoded)](https://img.shields.io/badge/code%20quality-10-brightgreen)](https://gitlab.com/crafty-controller/crafty-4)
[![Build Status](https://gitlab.com/crafty-controller/crafty-4/badges/master/pipeline.svg)](https://gitlab.com/crafty-controller/crafty-4/-/commits/master)
# Crafty Controller 4.0.0-beta
# Crafty Controller 4.0.1-beta
> Python based Control Panel for your Minecraft Server
## What is Crafty Controller?

View File

@ -40,7 +40,7 @@ class RolesController:
for key in role_data:
if key == "role_id":
continue
elif key == "servers":
if key == "servers":
added_servers = set(role_data["servers"]).difference(
set(base_data["servers"])
)
@ -175,6 +175,5 @@ class RolesController:
role["servers"] = server_ids
# logger.debug("role: ({}) {}".format(role_id, role))
return role
else:
# logger.debug("role: ({}) {}".format(role_id, {}))
return {}
# logger.debug("role: ({}) {}".format(role_id, {}))
return {}

View File

@ -140,7 +140,7 @@ class UsersController:
for key in user_data:
if key == "user_id":
continue
elif key == "roles":
if key == "roles":
added_roles = set(user_data["roles"]).difference(
set(base_data["roles"])
)

View File

@ -120,8 +120,7 @@ class BedrockPing:
ret["server_port_ipv4"] = server_info[10]
ret["server_port_ipv6"] = server_info[11]
return ret
else:
raise ValueError(f"Incorrect packet type ({data[0]} detected")
raise ValueError(f"Incorrect packet type ({data[0]} detected")
def ping(self, retries=3):
rtr = retries

View File

@ -104,12 +104,9 @@ def get_code_format(format_name):
if format_name in data.keys():
return data.get(format_name)
else:
logger.error(f"Format MOTD Error: format name {format_name} does not exist")
Console.error(
f"Format MOTD Error: format name {format_name} does not exist"
)
return ""
logger.error(f"Format MOTD Error: format name {format_name} does not exist")
Console.error(f"Format MOTD Error: format name {format_name} does not exist")
return ""
except Exception as e:
logger.critical(f"Config File Error: Unable to read {format_file} due to {e}")
@ -154,10 +151,7 @@ def ping(ip, port):
sock.sendall(data + b"\x01\x00") # handshake + status ping
length = read_var_int() # full packet length
if length < 10:
if length < 0:
return False
else:
return False
return not length < 0
sock.recv(1) # packet type, 0 for pings
length = read_var_int() # string length

View File

@ -43,8 +43,7 @@ class ServerProps:
if key in self.props.keys():
self.props[key] = val
return True
else:
return False
return False
def save(self):
# Writes to the new file

View File

@ -134,10 +134,8 @@ class Stats:
@staticmethod
def _get_process_stats(process):
if process is None:
process_stats = {"cpu_usage": 0, "memory_usage": 0, "mem_percentage": 0}
return process_stats
else:
process_pid = process.pid
return {"cpu_usage": 0, "memory_usage": 0, "mem_percentage": 0}
process_pid = process.pid
try:
p = psutil.Process(process_pid)
dummy = p.cpu_percent()
@ -162,13 +160,7 @@ class Stats:
logger.error(
f"Unable to get process details for pid: {process_pid} Error: {e}"
)
# Dummy Data
process_stats = {
"cpu_usage": 0,
"memory_usage": 0,
}
return process_stats
return {"cpu_usage": 0, "memory_usage": 0, "mem_percentage": 0}
@staticmethod
def _try_all_disk_usage():
@ -220,7 +212,7 @@ class Stats:
return level_total_size
def get_server_players(self, server_id):
def get_server_players(self, server_id): # pylint: disable=no-self-use
server = HelperServers.get_server_data_by_id(server_id)

View File

@ -213,18 +213,17 @@ class PermissionsCrafty:
user = HelperUsers.get_user(key.user_id)
if user["superuser"] and key.superuser:
return PermissionsCrafty.get_permissions_list()
if user["superuser"]:
# User is superuser but API key isn't
user_permissions_mask = "111"
else:
if user["superuser"]:
# User is superuser but API key isn't
user_permissions_mask = "111"
else:
# Not superuser
user_permissions_mask = PermissionsCrafty.get_crafty_permissions_mask(
user["user_id"]
)
key_permissions_mask: str = key.crafty_permissions
permissions_mask = PermissionHelper.combine_masks(
user_permissions_mask, key_permissions_mask
# Not superuser
user_permissions_mask = PermissionsCrafty.get_crafty_permissions_mask(
user["user_id"]
)
permissions_list = PermissionsCrafty.get_permissions(permissions_mask)
return permissions_list
key_permissions_mask: str = key.crafty_permissions
permissions_mask = PermissionHelper.combine_masks(
user_permissions_mask, key_permissions_mask
)
permissions_list = PermissionsCrafty.get_permissions(permissions_mask)
return permissions_list

View File

@ -265,24 +265,23 @@ class PermissionsServers:
user = HelperUsers.get_user(key.user_id)
if user["superuser"] and key.superuser:
return PermissionsServers.get_permissions_list()
else:
roles_list = HelperUsers.get_user_roles_id(user["user_id"])
role_server = (
RoleServers.select()
.where(RoleServers.role_id.in_(roles_list))
.where(RoleServers.server_id == server_id)
.execute()
)
try:
user_permissions_mask = role_server[0].permissions
except:
if user["superuser"]:
user_permissions_mask = "11111111"
else:
user_permissions_mask = "00000000"
key_permissions_mask = key.server_permissions
permissions_mask = PermissionHelper.combine_masks(
user_permissions_mask, key_permissions_mask
)
permissions_list = PermissionsServers.get_permissions(permissions_mask)
return permissions_list
roles_list = HelperUsers.get_user_roles_id(user["user_id"])
role_server = (
RoleServers.select()
.where(RoleServers.role_id.in_(roles_list))
.where(RoleServers.server_id == server_id)
.execute()
)
try:
user_permissions_mask = role_server[0].permissions
except:
if user["superuser"]:
user_permissions_mask = "11111111"
else:
user_permissions_mask = "00000000"
key_permissions_mask = key.server_permissions
permissions_mask = PermissionHelper.combine_masks(
user_permissions_mask, key_permissions_mask
)
permissions_list = PermissionsServers.get_permissions(permissions_mask)
return permissions_list

View File

@ -148,9 +148,8 @@ class HelperUsers:
# I know it should apply it without setting it but I'm just making sure
user = HelperUsers.add_user_roles(user)
return user
else:
# logger.debug("user: ({}) {}".format(user_id, {}))
return {}
# logger.debug("user: ({}) {}".format(user_id, {}))
return {}
@staticmethod
def get_user_columns(

View File

@ -62,8 +62,7 @@ class Authentication:
if int(user.get("valid_tokens_from").timestamp()) < iat:
# Success!
return key, data, user
else:
return None
return None
def check_err(
self,

View File

@ -1,5 +1,7 @@
import contextlib
import os
import re
import winreg
import sys
import json
import tempfile
@ -93,16 +95,50 @@ class Helpers:
if Helpers.check_file_exists(file):
file_time = os.path.getmtime(file)
# Check against 24 hours
if (time.time() - file_time) / 3600 > 24 * days:
return True
else:
return False
return (time.time() - file_time) / 3600 > 24 * days
logger.error(f"{file} does not exist")
return True
def get_servers_root_dir(self):
return self.servers_dir
@staticmethod
def which_java():
# Adapted from LeeKamentsky >>>
# https://github.com/LeeKamentsky/python-javabridge/blob/master/javabridge/locate.py
jdk_key_paths = (
"SOFTWARE\\JavaSoft\\JDK",
"SOFTWARE\\JavaSoft\\Java Development Kit",
)
for jdk_key_path in jdk_key_paths:
try:
with winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, jdk_key_path) as kjdk:
kjdk_values = (
dict( # pylint: disable=consider-using-dict-comprehension
[
winreg.EnumValue(kjdk, i)[:2]
for i in range(winreg.QueryInfoKey(kjdk)[1])
]
)
)
current_version = kjdk_values["CurrentVersion"]
kjdk_current = winreg.OpenKey(
winreg.HKEY_LOCAL_MACHINE, jdk_key_path + "\\" + current_version
)
kjdk_current_values = (
dict( # pylint: disable=consider-using-dict-comprehension
[
winreg.EnumValue(kjdk_current, i)[:2]
for i in range(winreg.QueryInfoKey(kjdk_current)[1])
]
)
)
return kjdk_current_values["JavaHome"]
except WindowsError as e: # pylint: disable=E0602
if e.errno == 2:
continue
raise
@staticmethod
def check_internet():
try:
@ -125,10 +161,7 @@ class Helpers:
a_socket.close()
if result_of_check == 0:
return True
else:
return False
return result_of_check == 0
@staticmethod
def check_server_conn(server_port):
@ -140,10 +173,7 @@ class Helpers:
result_of_check = a_socket.connect_ex(location)
a_socket.close()
if result_of_check == 0:
return True
else:
return False
return result_of_check == 0
@staticmethod
def cmdparse(cmd_in):
@ -163,10 +193,9 @@ class Helpers:
# Continue the loop.
if char == " ":
continue
else:
cmd_index += 1
cmd_out.append("")
new_param = False
cmd_index += 1
cmd_out.append("")
new_param = False
if esc: # if we encountered an escape character on the last loop,
# append this char regardless of what it is
if char not in Helpers.allowed_quotes:
@ -348,8 +377,7 @@ class Helpers:
common_path = pathlib.Path(os.path.commonpath([base, fileabs]))
if base == common_path:
return fileabs
else:
raise ValueError("Path traversal detected")
raise ValueError("Path traversal detected")
@staticmethod
def tail_file(file_name, number_lines=20):
@ -405,15 +433,8 @@ class Helpers:
@staticmethod
def check_root():
if Helpers.is_os_windows():
if ctypes.windll.shell32.IsUserAnAdmin() == 1:
return True
else:
return False
else:
if os.geteuid() == 0:
return True
else:
return False
return ctypes.windll.shell32.IsUserAnAdmin() == 1
return os.geteuid() == 0
@staticmethod
def unzip_file(zip_path):
@ -491,9 +512,10 @@ class Helpers:
# del any old session.lock file as this is a new session
try:
os.remove(session_log_file)
with contextlib.suppress(FileNotFoundError):
os.remove(session_log_file)
except Exception as e:
logger.error(f"Deleting Session.lock failed with error: {e}")
Console.error(f"Deleting logs/session.log failed with error: {e}")
@staticmethod
def get_time_as_string():
@ -529,8 +551,7 @@ class Helpers:
if os.path.exists(path) and os.path.isfile(path):
logger.debug(f"Found path: {path}")
return True
else:
return False
return False
@staticmethod
def human_readable_file_size(num: int, suffix="B"):
@ -551,8 +572,7 @@ class Helpers:
if os.path.exists(path):
logger.debug(f"Found path: {path}")
return True
else:
return False
return False
@staticmethod
def get_file_contents(path: str, lines=100):
@ -768,10 +788,7 @@ class Helpers:
@staticmethod
def is_os_windows():
if os.name == "nt":
return True
else:
return False
return os.name == "nt"
@staticmethod
def wtol_path(w_path):
@ -946,8 +963,7 @@ class Helpers:
# extracts archive to temp directory
zip_ref.extractall(temp_dir)
return temp_dir
else:
return False
return False
@staticmethod
def in_path(parent_path, child_path):

View File

@ -234,6 +234,23 @@ class ServerInstance:
self.settings["executable"]
)
self.server_command = Helpers.cmdparse(self.settings["execution_command"])
if self.helper.is_os_windows() and self.server_command[0] == "java":
logger.info(
"Detected nebulous java in start command. "
"Replacing with full java path."
)
which_java_raw = self.helper.which_java()
java_path = which_java_raw + "\\bin\\java"
if str(which_java_raw) != str(self.helper.get_servers_root_dir) or str(
self.helper.get_servers_root_dir
) in str(which_java_raw):
self.server_command[0] = java_path
else:
logger.critcal(
"Possible attack detected. User attempted to exec "
"java binary from server directory."
)
return
self.server_path = Helpers.get_os_understandable_path(self.settings["path"])
# let's do some quick checking to make sure things actually exists
@ -409,10 +426,9 @@ class ServerInstance:
},
)
return False
else:
logger.error(
f"Server {self.name} failed to start with error code: {ex}"
)
logger.error(
f"Server {self.name} failed to start with error code: {ex}"
)
if user_id:
self.helper.websocket_helper.broadcast_user(
user_id,
@ -646,9 +662,8 @@ class ServerInstance:
poll = self.process.poll()
if poll is None:
return True
else:
self.last_rc = poll
return False
self.last_rc = poll
return False
def send_command(self, command):
if not self.check_running() and command.lower() != "start":
@ -685,16 +700,15 @@ class ServerInstance:
)
self.run_threaded_server(None)
return True
else:
logger.critical(
f"The server {name} has crashed, "
f"crash detection is disabled and it will not be restarted"
)
Console.critical(
f"The server {name} has crashed, "
f"crash detection is disabled and it will not be restarted"
)
return False
logger.critical(
f"The server {name} has crashed, "
f"crash detection is disabled and it will not be restarted"
)
Console.critical(
f"The server {name} has crashed, "
f"crash detection is disabled and it will not be restarted"
)
return False
def kill(self):
logger.info(f"Terminating server {self.server_id} and all child processes")
@ -720,16 +734,10 @@ class ServerInstance:
self.process.kill()
def get_start_time(self):
if self.check_running():
return self.start_time
else:
return False
return self.start_time if self.check_running() else False
def get_pid(self):
if self.process is not None:
return self.process.pid
else:
return None
return self.process.pid if self.process is not None else None
def detect_crash(self):
@ -793,12 +801,6 @@ class ServerInstance:
f.close()
self.run_threaded_server(user_id)
def is_backup_running(self):
if self.is_backingup:
return True
else:
return False
def backup_server(self):
if self.settings["backup_path"] == "":
logger.critical("Backup path is None. Canceling Backup!")
@ -988,34 +990,32 @@ class ServerInstance:
return {"percent": 0, "total_files": 0}
def list_backups(self):
if self.settings["backup_path"]:
if Helpers.check_path_exists(
Helpers.get_os_understandable_path(self.settings["backup_path"])
):
files = Helpers.get_human_readable_files_sizes(
Helpers.list_dir_by_date(
Helpers.get_os_understandable_path(self.settings["backup_path"])
)
)
return [
{
"path": os.path.relpath(
f["path"],
start=Helpers.get_os_understandable_path(
self.settings["backup_path"]
),
),
"size": f["size"],
}
for f in files
]
else:
return []
else:
if not self.settings["backup_path"]:
logger.info(
f"Error putting backup file list for server with ID: {self.server_id}"
)
return []
if not Helpers.check_path_exists(
Helpers.get_os_understandable_path(self.settings["backup_path"])
):
return []
files = Helpers.get_human_readable_files_sizes(
Helpers.list_dir_by_date(
Helpers.get_os_understandable_path(self.settings["backup_path"])
)
)
return [
{
"path": os.path.relpath(
f["path"],
start=Helpers.get_os_understandable_path(
self.settings["backup_path"]
),
),
"size": f["size"],
}
for f in files
]
def jar_update(self):
self.stats_helper.set_update(True)
@ -1025,11 +1025,7 @@ class ServerInstance:
update_thread.start()
def check_update(self):
if self.stats_helper.get_server_stats()["updating"]:
return True
else:
return False
return self.stats_helper.get_server_stats()["updating"]
def a_jar_update(self):
was_started = "-1"

View File

@ -31,10 +31,10 @@ class Translation:
if isinstance(translated_word, dict):
# JSON objects
return json.dumps(translated_word)
elif isinstance(translated_word, str):
if isinstance(translated_word, str):
# Basic strings
return translated_word
elif hasattr(translated_word, "__iter__"):
if hasattr(translated_word, "__iter__"):
# Multiline strings
return "\n".join(translated_word)
return "Error while getting translation"

View File

@ -242,8 +242,7 @@ class AjaxHandler(BaseHandler):
if not self.check_server_id(server_id, "get_tree"):
return
else:
server_id = bleach.clean(server_id)
server_id = bleach.clean(server_id)
if Helpers.validate_traversal(
self.controller.servers.get_server_data_by_id(server_id)["path"], path
@ -495,8 +494,7 @@ class AjaxHandler(BaseHandler):
if not self.check_server_id(server_id, "del_backup"):
return
else:
server_id = bleach.clean(server_id)
server_id = bleach.clean(server_id)
server_info = self.controller.servers.get_server_data_by_id(server_id)
if not (
@ -576,16 +574,15 @@ class AjaxHandler(BaseHandler):
f"Server ID not defined in {page_name} ajax call ({server_id})"
)
return
else:
server_id = bleach.clean(server_id)
server_id = bleach.clean(server_id)
# does this server id exist?
if not self.controller.servers.server_id_exists(server_id):
logger.warning(
f"Server ID not found in {page_name} ajax call ({server_id})"
)
Console.warning(
f"Server ID not found in {page_name} ajax call ({server_id})"
)
return
# does this server id exist?
if not self.controller.servers.server_id_exists(server_id):
logger.warning(
f"Server ID not found in {page_name} ajax call ({server_id})"
)
Console.warning(
f"Server ID not found in {page_name} ajax call ({server_id})"
)
return
return True

View File

@ -74,10 +74,9 @@ class ApiHandler(BaseHandler):
logger.info(f"User {user_data['username']} has authenticated to API")
return True # This is to set the "authenticated"
else:
logging.debug("Auth unsuccessful")
self.access_denied("unknown", "the user provided an invalid token")
return False
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(
@ -225,7 +224,7 @@ class StartServer(ApiHandler):
):
self.access_denied("unknown")
return
elif not self.permissions[
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

View File

@ -88,13 +88,11 @@ class BaseHandler(tornado.web.RequestHandler):
if r in name:
logger.debug(f"Auto-bleaching {name}: [**REDACTED**]")
break
else:
logger.debug(f"Auto-bleaching {name}: {text}")
logger.debug(f"Auto-bleaching {name}: {text}")
if type(text) in self.nobleach:
logger.debug("Auto-bleaching - bypass type")
return text
else:
return bleach.clean(text)
return bleach.clean(text)
def get_argument(
self,
@ -216,10 +214,9 @@ class BaseHandler(tornado.web.RequestHandler):
superuser,
user,
)
else:
logging.debug("Auth unsuccessful")
self.access_denied(None, "the user provided an invalid token")
return None
logging.debug("Auth unsuccessful")
self.access_denied(None, "the user provided an invalid token")
return None
except Exception as auth_exception:
logger.debug(
"An error occured while authenticating an API user:",

View File

@ -55,8 +55,7 @@ class FileHandler(BaseHandler):
if not self.check_server_id(server_id, "get_file"):
return
else:
server_id = bleach.clean(server_id)
server_id = bleach.clean(server_id)
if not Helpers.in_path(
Helpers.get_os_understandable_path(
@ -93,8 +92,7 @@ class FileHandler(BaseHandler):
if not self.check_server_id(server_id, "get_tree"):
return
else:
server_id = bleach.clean(server_id)
server_id = bleach.clean(server_id)
if Helpers.validate_traversal(
self.controller.servers.get_server_data_by_id(server_id)["path"], path
@ -115,8 +113,7 @@ class FileHandler(BaseHandler):
if not self.check_server_id(server_id, "get_tree"):
return
else:
server_id = bleach.clean(server_id)
server_id = bleach.clean(server_id)
if Helpers.validate_traversal(
self.controller.servers.get_server_data_by_id(server_id)["path"], path
@ -164,8 +161,7 @@ class FileHandler(BaseHandler):
if not self.check_server_id(server_id, "create_file"):
return
else:
server_id = bleach.clean(server_id)
server_id = bleach.clean(server_id)
if not Helpers.in_path(
Helpers.get_os_understandable_path(
@ -198,8 +194,7 @@ class FileHandler(BaseHandler):
if not self.check_server_id(server_id, "create_dir"):
return
else:
server_id = bleach.clean(server_id)
server_id = bleach.clean(server_id)
if not Helpers.in_path(
Helpers.get_os_understandable_path(
@ -264,8 +259,7 @@ class FileHandler(BaseHandler):
if not self.check_server_id(server_id, "del_file"):
return
else:
server_id = bleach.clean(server_id)
server_id = bleach.clean(server_id)
server_info = self.controller.servers.get_server_data_by_id(server_id)
if not (
@ -299,8 +293,7 @@ class FileHandler(BaseHandler):
if not self.check_server_id(server_id, "del_dir"):
return
else:
server_id = bleach.clean(server_id)
server_id = bleach.clean(server_id)
server_info = self.controller.servers.get_server_data_by_id(server_id)
if not Helpers.in_path(
@ -353,8 +346,7 @@ class FileHandler(BaseHandler):
if not self.check_server_id(server_id, "save_file"):
return
else:
server_id = bleach.clean(server_id)
server_id = bleach.clean(server_id)
if not Helpers.in_path(
Helpers.get_os_understandable_path(
@ -388,8 +380,7 @@ class FileHandler(BaseHandler):
if not self.check_server_id(server_id, "rename_file"):
return
else:
server_id = bleach.clean(server_id)
server_id = bleach.clean(server_id)
if item_path is None or new_item_name is None:
logger.warning("Invalid path(s) in rename_file file ajax call")
@ -464,8 +455,7 @@ class FileHandler(BaseHandler):
if not self.check_server_id(server_id, "rename_file"):
return
else:
server_id = bleach.clean(server_id)
server_id = bleach.clean(server_id)
if item_path is None or new_item_name is None:
logger.warning("Invalid path(s) in rename_file file ajax call")
@ -514,16 +504,15 @@ class FileHandler(BaseHandler):
f"Server ID not defined in {page_name} file ajax call ({server_id})"
)
return
else:
server_id = bleach.clean(server_id)
server_id = bleach.clean(server_id)
# does this server id exist?
if not self.controller.servers.server_id_exists(server_id):
logger.warning(
f"Server ID not found in {page_name} file ajax call ({server_id})"
)
Console.warning(
f"Server ID not found in {page_name} file ajax call ({server_id})"
)
return
# does this server id exist?
if not self.controller.servers.server_id_exists(server_id):
logger.warning(
f"Server ID not found in {page_name} file ajax call ({server_id})"
)
Console.warning(
f"Server ID not found in {page_name} file ajax call ({server_id})"
)
return
return True

View File

@ -158,34 +158,31 @@ class PanelHandler(BaseHandler):
if server_id is None:
self.redirect("/panel/error?error=Invalid Server ID")
return None
else:
# Does this server exist?
if not self.controller.servers.server_id_exists(server_id):
# Does this server exist?
if not self.controller.servers.server_id_exists(server_id):
self.redirect("/panel/error?error=Invalid Server ID")
return None
# Does the user have permission?
if superuser: # TODO: Figure out a better solution
return server_id
if api_key is not None:
if not self.controller.servers.server_id_authorized_api_key(
server_id, api_key
):
logger.debug(
f"API key {api_key.name} (id: {api_key.token_id}) "
f"does not have permission"
)
self.redirect("/panel/error?error=Invalid Server ID")
return None
else:
if not self.controller.servers.server_id_authorized(
server_id, exec_user["user_id"]
):
logger.debug(f'User {exec_user["user_id"]} does not have permission')
self.redirect("/panel/error?error=Invalid Server ID")
return None
# Does the user have permission?
if not superuser: # TODO: Figure out a better solution
if api_key is not None:
if not self.controller.servers.server_id_authorized_api_key(
server_id, api_key
):
logger.debug(
f"API key {api_key.name} (id: {api_key.token_id}) "
f"does not have permission"
)
self.redirect("/panel/error?error=Invalid Server ID")
return None
else:
if not self.controller.servers.server_id_authorized(
server_id, exec_user["user_id"]
):
logger.debug(
f'User {exec_user["user_id"]} does not have permission'
)
self.redirect("/panel/error?error=Invalid Server ID")
return None
return server_id
# Server fetching, spawned asynchronously
# TODO: Make the related front-end elements update with AJAX
@ -1026,7 +1023,7 @@ class PanelHandler(BaseHandler):
if user_id is None:
self.redirect("/panel/error?error=Invalid User ID")
return
elif EnumPermissionsCrafty.USER_CONFIG not in exec_user_crafty_permissions:
if EnumPermissionsCrafty.USER_CONFIG not in exec_user_crafty_permissions:
if str(user_id) != str(exec_user["user_id"]):
self.redirect(
"/panel/error?error=Unauthorized access: not a user editor"
@ -1074,23 +1071,22 @@ class PanelHandler(BaseHandler):
self.redirect("/panel/error?error=Unauthorized access: not superuser")
return
elif str(exec_user["user_id"]) == str(user_id):
if str(exec_user["user_id"]) == str(user_id):
self.redirect(
"/panel/error?error=Unauthorized access: you cannot delete yourself"
)
return
elif user_id is None:
if user_id is None:
self.redirect("/panel/error?error=Invalid User ID")
return
else:
# does this user id exist?
target_user = self.controller.users.get_user_by_id(user_id)
if not target_user:
self.redirect("/panel/error?error=Invalid User ID")
return
elif target_user["superuser"]:
self.redirect("/panel/error?error=Cannot remove a superuser")
return
# does this user id exist?
target_user = self.controller.users.get_user_by_id(user_id)
if not target_user:
self.redirect("/panel/error?error=Invalid User ID")
return
if target_user["superuser"]:
self.redirect("/panel/error?error=Cannot remove a superuser")
return
self.controller.users.remove_user(user_id)
@ -1170,7 +1166,7 @@ class PanelHandler(BaseHandler):
"/panel/error?error=Unauthorized access: not a role editor"
)
return
elif role_id is None:
if role_id is None:
self.redirect("/panel/error?error=Invalid Role ID")
return
@ -1182,15 +1178,14 @@ class PanelHandler(BaseHandler):
if not superuser:
self.redirect("/panel/error?error=Unauthorized access: not superuser")
return
elif role_id is None:
if role_id is None:
self.redirect("/panel/error?error=Invalid Role ID")
return
# does this user id exist?
target_role = self.controller.roles.get_role(role_id)
if not target_role:
self.redirect("/panel/error?error=Invalid Role ID")
return
else:
# does this user id exist?
target_role = self.controller.roles.get_role(role_id)
if not target_role:
self.redirect("/panel/error?error=Invalid Role ID")
return
self.controller.roles.remove_role(role_id)
@ -1808,6 +1803,12 @@ class PanelHandler(BaseHandler):
else:
superuser = False
if not exec_user["superuser"]:
if username is None or username == "":
self.redirect("/panel/error?error=Invalid username")
return
if user_id is None:
self.redirect("/panel/error?error=Invalid User ID")
return
if (
EnumPermissionsCrafty.USER_CONFIG
not in exec_user_crafty_permissions
@ -1835,17 +1836,10 @@ class PanelHandler(BaseHandler):
)
self.redirect("/panel/panel_config")
return
elif username is None or username == "":
self.redirect("/panel/error?error=Invalid username")
return
elif user_id is None:
# does this user id exist?
if not self.controller.users.user_id_exists(user_id):
self.redirect("/panel/error?error=Invalid User ID")
return
else:
# does this user id exist?
if not self.controller.users.user_id_exists(user_id):
self.redirect("/panel/error?error=Invalid User ID")
return
else:
if password0 != password1:
self.redirect("/panel/error?error=Passwords must match")
@ -1892,14 +1886,13 @@ class PanelHandler(BaseHandler):
if name is None or name == "":
self.redirect("/panel/error?error=Invalid API key name")
return
elif user_id is None:
if user_id is None:
self.redirect("/panel/error?error=Invalid User ID")
return
# does this user id exist?
if not self.controller.users.user_id_exists(user_id):
self.redirect("/panel/error?error=Invalid User ID")
return
else:
# does this user id exist?
if not self.controller.users.user_id_exists(user_id):
self.redirect("/panel/error?error=Invalid User ID")
return
crafty_permissions_mask = self.get_perms()
server_permissions_mask = self.get_perms_server()
@ -1928,12 +1921,11 @@ class PanelHandler(BaseHandler):
if key_id is None:
self.redirect("/panel/error?error=Invalid Key ID")
return
else:
key = self.controller.users.get_user_api_key(key_id)
# does this user id exist?
if key is None:
self.redirect("/panel/error?error=Invalid Key ID")
return
key = self.controller.users.get_user_api_key(key_id)
# does this user id exist?
if key is None:
self.redirect("/panel/error?error=Invalid Key ID")
return
self.controller.management.add_to_audit_log(
exec_user["user_id"],
@ -1951,14 +1943,14 @@ class PanelHandler(BaseHandler):
self.finish()
elif page == "add_user":
if bleach.clean(self.get_argument("username", None)).lower() == "system":
username = bleach.clean(self.get_argument("username", None))
if username.lower() == "system":
self.redirect(
"/panel/error?error=Unauthorized access: "
"username system is reserved for the Crafty system."
" Please choose a different username."
)
return
username = bleach.clean(self.get_argument("username", None))
password0 = bleach.clean(self.get_argument("password0", None))
password1 = bleach.clean(self.get_argument("password1", None))
email = bleach.clean(self.get_argument("email", "default@example.com"))
@ -1991,14 +1983,13 @@ class PanelHandler(BaseHandler):
"/panel/error?error=Unauthorized access: quantity limit reached"
)
return
elif username is None or username == "":
if username is None or username == "":
self.redirect("/panel/error?error=Invalid username")
return
else:
# does this user id exist?
if self.controller.users.get_id_by_name(username) is not None:
self.redirect("/panel/error?error=User exists")
return
# does this user id exist?
if self.controller.users.get_id_by_name(username) is not None:
self.redirect("/panel/error?error=User exists")
return
if password0 != password1:
self.redirect("/panel/error?error=Passwords must match")
@ -2047,17 +2038,16 @@ class PanelHandler(BaseHandler):
"/panel/error?error=Unauthorized access: not a role editor"
)
return
elif role_name is None or role_name == "":
if role_name is None or role_name == "":
self.redirect("/panel/error?error=Invalid username")
return
elif role_id is None:
if role_id is None:
self.redirect("/panel/error?error=Invalid Role ID")
return
# does this user id exist?
if not self.controller.roles.role_id_exists(role_id):
self.redirect("/panel/error?error=Invalid Role ID")
return
else:
# does this user id exist?
if not self.controller.roles.role_id_exists(role_id):
self.redirect("/panel/error?error=Invalid Role ID")
return
servers = self.get_role_servers()
@ -2079,7 +2069,7 @@ class PanelHandler(BaseHandler):
"/panel/error?error=Unauthorized access: not a role editor"
)
return
elif (
if (
not self.controller.crafty_perms.can_add_role(exec_user["user_id"])
and not exec_user["superuser"]
):
@ -2087,14 +2077,13 @@ class PanelHandler(BaseHandler):
"/panel/error?error=Unauthorized access: quantity limit reached"
)
return
elif role_name is None or role_name == "":
if role_name is None or role_name == "":
self.redirect("/panel/error?error=Invalid role name")
return
else:
# does this user id exist?
if self.controller.roles.get_roleid_by_name(role_name) is not None:
self.redirect("/panel/error?error=Role exists")
return
# does this user id exist?
if self.controller.roles.get_roleid_by_name(role_name) is not None:
self.redirect("/panel/error?error=Role exists")
return
servers = self.get_role_servers()
@ -2145,17 +2134,14 @@ class PanelHandler(BaseHandler):
if not superuser:
self.redirect("/panel/error?error=Unauthorized access: not superuser")
return
elif (
key_id is None or self.controller.users.get_user_api_key(key_id) is None
):
if key_id is None or self.controller.users.get_user_api_key(key_id) is None:
self.redirect("/panel/error?error=Invalid Key ID")
return
# does this user id exist?
target_key = self.controller.users.get_user_api_key(key_id)
if not target_key:
self.redirect("/panel/error?error=Invalid Key ID")
return
else:
# does this user id exist?
target_key = self.controller.users.get_user_api_key(key_id)
if not target_key:
self.redirect("/panel/error?error=Invalid Key ID")
return
self.controller.users.delete_user_api_key(key_id)

View File

@ -1,6 +1,6 @@
{
"major": 4,
"minor": 0,
"sub": 0,
"sub": 1,
"meta": "beta"
}
}

View File

@ -450,7 +450,7 @@
"absoluteZipPath": "Absoluut pad naar de server",
"addRole": "Server toevoegen aan bestaande rol(len)",
"autoCreate": "Als er niks is geselecteerd maakt Crafty er één.",
"bePatient": "Wees geduldig, we' + (importing ? 'importeren' : 'downloaden') + ' de server",
"bePatient": "Wees geduldig, we ' + (importing ? 'importeren' : 'downloaden') + ' de server",
"buildServer": "Maak server!",
"clickRoot": "Klik hier om je root-map te selecteren",
"close": "Sluiten",

View File

@ -1,7 +1,7 @@
<?xml version="1.0"?>
<Container version="2">
<Beta>True</Beta>
<Name>Crafty</Name>
<Name>Crafty-4</Name>
<Repository>registry.gitlab.com/crafty-controller/crafty-4:latest</Repository>
<Registry>registry.gitlab.com/crafty-controller/crafty-4</Registry>
<Network>bridge</Network>
@ -40,11 +40,6 @@ For migration from 3.x please refer to the documentation: https://wiki.craftycon
<ContainerPort>8443</ContainerPort>
<Protocol>tcp</Protocol>
</Port>
<Port>
<HostPort>8000</HostPort>
<ContainerPort>8000</ContainerPort>
<Protocol>tcp</Protocol>
</Port>
<Port>
<HostPort>25500-25600</HostPort>
<ContainerPort>25500-25600</ContainerPort>
@ -64,41 +59,40 @@ For migration from 3.x please refer to the documentation: https://wiki.craftycon
</Networking>
<Data>
<Volume>
<HostDir>/mnt/user/appdata/Crafty-4/servers/</HostDir>
<HostDir>/mnt/user/appdata/crafty-4/servers/</HostDir>
<ContainerDir>/crafty/servers</ContainerDir>
<Mode>rw</Mode>
</Volume>
<Volume>
<HostDir>/mnt/user/appdata/Crafty-4/backups/</HostDir>
<HostDir>/mnt/user/appdata/crafty-4/backups/</HostDir>
<ContainerDir>/crafty/backups</ContainerDir>
<Mode>rw</Mode>
</Volume>
<Volume>
<HostDir>/mnt/user/appdata/Crafty-4/logs/</HostDir>
<HostDir>/mnt/user/appdata/crafty-4/logs/</HostDir>
<ContainerDir>/crafty/logs</ContainerDir>
<Mode>rw</Mode>
</Volume>
<Volume>
<HostDir>/mnt/user/appdata/Crafty-4/config/</HostDir>
<HostDir>/mnt/user/appdata/crafty-4/config/</HostDir>
<ContainerDir>/crafty/app/config</ContainerDir>
<Mode>rw</Mode>
</Volume>
<Volume>
<HostDir>/mnt/user/appdata/Crafty-4/import/</HostDir>
<HostDir>/mnt/user/appdata/crafty-4/import/</HostDir>
<ContainerDir>/crafty/import</ContainerDir>
<Mode>rw</Mode>
</Volume>
</Data>
<Environment/>
<Labels/>
<Config Name="Port for https traffic" Target="8443" Default="8443" Mode="tcp" Description="Port for https traffic to the web Ui" Type="Port" Display="always-hide" Required="true" Mask="false">8443</Config>
<Config Name="Port for http redirections" Target="8000" Default="1800" Mode="tcp" Description="http traffic will automatically be redirected to htps" Type="Port" Display="always-hide" Required="true" Mask="false">8000</Config>
<Config Name="Web UI Port" Target="8443" Default="8443" Mode="tcp" Description="Web UI [HTTPS]" Type="Port" Display="always-hide" Required="true" Mask="false">8443</Config>
<Config Name="Minecraft ports" Target="25500-25600" Default="25500-25600" Mode="tcp" Description="Container Port: 25500-25600 yes, 100 ports for 100 possible Servers" Type="Port" Display="always-hide" Required="true" Mask="false">25500-25600</Config>
<Config Name="Port for dynmap" Target="8123" Default="8123" Mode="tcp" Description="Dynmap port" Type="Port" Display="always-hide" Required="true" Mask="false">8123</Config>
<Config Name="Port for dynmap" Target="8123" Default="8123" Mode="tcp" Description="Dynmap Port" Type="Port" Display="always-hide" Required="true" Mask="false">8123</Config>
<Config Name="Port for bedrock server" Target="19132" Default="19132" Mode="udp" Description="Bedrock server port" Type="Port" Display="always-hide" Required="true" Mask="false">19132</Config>
<Config Name="Server files" Target="/crafty/servers" Default="/mnt/user/appdata/Crafty-4/servers/" Mode="rw" Description="Path to the minecraft server folders" Type="Path" Display="always-hide" Required="true" Mask="false">/mnt/user/appdata/Crafty-4/servers/</Config>
<Config Name="Backup files" Target="/crafty/backups" Default="/mnt/user/appdata/Crafty-4/backups/" Mode="rw" Description="Server Backups" Type="Path" Display="always-hide" Required="true" Mask="false">/mnt/user/appdata/Crafty-4/backups/</Config>
<Config Name="Server Logs" Target="/crafty/logs" Default="/mnt/user/appdata/Crafty-4/logs/" Mode="rw" Description="Logs" Type="Path" Display="advanced-hide" Required="true" Mask="false">/mnt/user/appdata/Crafty-4/logs/</Config>
<Config Name="Crafty Configuration" Target="/crafty/app/config" Default="/mnt/user/appdata/Crafty-4/config/" Mode="rw" Description="Path to the persistent Crafty files" Type="Path" Display="advanced-hide" Required="true" Mask="false">/mnt/user/appdata/Crafty-4/config/</Config>
<Config Name="Import folder" Target="/crafty/import" Default="/mnt/user/appdata/Crafty-4/import/" Mode="rw" Description="Imports" Type="Path" Display="advanced-hide" Required="true" Mask="false">/mnt/user/appdata/Crafty-4/import/</Config>
<Config Name="Server files" Target="/crafty/servers" Default="/mnt/user/appdata/crafty-4/servers/" Mode="rw" Description="Path to the minecraft server folders" Type="Path" Display="always-hide" Required="true" Mask="false">/mnt/user/appdata/crafty-4/servers/</Config>
<Config Name="Backup files" Target="/crafty/backups" Default="/mnt/user/appdata/crafty-4/backups/" Mode="rw" Description="Server Backups" Type="Path" Display="always-hide" Required="true" Mask="false">/mnt/user/appdata/crafty-4/backups/</Config>
<Config Name="Server Logs" Target="/crafty/logs" Default="/mnt/user/appdata/crafty-4/logs/" Mode="rw" Description="Logs" Type="Path" Display="advanced-hide" Required="true" Mask="false">/mnt/user/appdata/crafty-4/logs/</Config>
<Config Name="Crafty Configuration" Target="/crafty/app/config" Default="/mnt/user/appdata/crafty-4/config/" Mode="rw" Description="Path to the persistent Crafty files" Type="Path" Display="advanced-hide" Required="true" Mask="false">/mnt/user/appdata/crafty-4/config/</Config>
<Config Name="Import folder" Target="/crafty/import" Default="/mnt/user/appdata/crafty-4/import/" Mode="rw" Description="Import existing Mincecraft Servers" Type="Path" Display="advanced-hide" Required="true" Mask="false">/mnt/user/appdata/crafty-4/import/</Config>
</Container>