From e0ce1d118c9b019522ac854e17a32c422da66d79 Mon Sep 17 00:00:00 2001 From: Iain Powrie Date: Wed, 26 Jan 2022 01:45:30 +0000 Subject: [PATCH] Create pylintrc, code review pipeline & correct codebase errors Fix uploads, Only send server stats to user page when they have access to servers --- .gitlab-ci.yml | 33 +- .pylintrc | 603 ++++++++++++++++++ .../controllers/crafty_perms_controller.py | 29 +- .../controllers/management_controller.py | 33 +- app/classes/controllers/roles_controller.py | 22 +- .../controllers/server_perms_controller.py | 39 +- app/classes/controllers/servers_controller.py | 47 +- app/classes/controllers/users_controller.py | 34 +- app/classes/minecraft/mc_ping.py | 20 +- app/classes/minecraft/server_props.py | 8 +- app/classes/minecraft/serverjars.py | 45 +- app/classes/minecraft/stats.py | 40 +- app/classes/models/crafty_permissions.py | 18 +- app/classes/models/management.py | 55 +- app/classes/models/roles.py | 13 +- app/classes/models/server_permissions.py | 20 +- app/classes/models/servers.py | 49 +- app/classes/models/users.py | 33 +- app/classes/shared/{cmd.py => command.py} | 29 +- app/classes/shared/console.py | 18 +- app/classes/shared/exceptions.py | 6 +- app/classes/shared/helpers.py | 230 +++---- app/classes/shared/installer.py | 4 +- app/classes/shared/main_controller.py | 170 +++-- app/classes/shared/main_models.py | 41 +- app/classes/shared/migration.py | 50 +- app/classes/shared/permission_helper.py | 1 - app/classes/shared/server.py | 212 +++--- app/classes/shared/tasks.py | 324 +++++++--- app/classes/shared/translation.py | 5 +- app/classes/web/ajax_handler.py | 230 +++---- app/classes/web/api_handler.py | 26 +- app/classes/web/base_handler.py | 9 +- app/classes/web/default_handler.py | 3 +- app/classes/web/http_handler.py | 23 +- app/classes/web/http_handler_page.py | 28 +- app/classes/web/panel_handler.py | 223 +++---- app/classes/web/public_handler.py | 30 +- app/classes/web/server_handler.py | 54 +- app/classes/web/static_handler.py | 8 +- app/classes/web/status_handler.py | 19 +- .../web/{tornado.py => tornado_handler.py} | 28 +- app/classes/web/upload_handler.py | 40 +- app/classes/web/websocket_handler.py | 25 +- app/classes/web/websocket_helper.py | 27 +- app/config/version.json | 2 +- app/frontend/templates/panel/dashboard.html | 4 +- app/translations/en_EN.json | 4 +- config_examples/nginx.conf.example | 2 + main.py | 47 +- 50 files changed, 1825 insertions(+), 1238 deletions(-) create mode 100644 .pylintrc rename app/classes/shared/{cmd.py => command.py} (78%) rename app/classes/web/{tornado.py => tornado_handler.py} (89%) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 1996376a..19b94a44 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,4 +1,5 @@ stages: +- test - prod-deployment - dev-deployment @@ -6,6 +7,36 @@ variables: DOCKER_HOST: tcp://docker:2376 DOCKER_TLS_CERTDIR: "/certs" +pylint: + stage: test + image: python:3.7-slim + services: + - name: docker:dind + tags: + - 'docker_testers' + rules: + - if: '$CI_PIPELINE_SOURCE == "merge_request_event"' + - if: '$CI_COMMIT_BRANCH && $CI_OPEN_MERGE_REQUESTS' + when: never + before_script: + - mkdir -p public/badges public/lint + - echo undefined > public/badges/$CI_JOB_NAME.score + - pip install pylint-gitlab + script: + - pylint --exit-zero --output-format=text $(find -type f -name "*.py" ! -path "**/.venv/**" ! -path "**/app/migrations/**") | tee /tmp/pylint.txt + - sed -n 's/^Your code has been rated at \([-0-9.]*\)\/.*/\1/p' /tmp/pylint.txt > public/badges/$CI_JOB_NAME.score + - pylint --exit-zero --output-format=pylint_gitlab.GitlabCodeClimateReporter $(find -type f -name "*.py" ! -path "**/.venv/**" ! -path "**/app/migrations/**") > codeclimate.json + after_script: + - anybadge --overwrite --label $CI_JOB_NAME --value=$(cat public/badges/$CI_JOB_NAME.score) --file=public/badges/$CI_JOB_NAME.svg 4=red 6=orange 8=yellow 10=green + - | + echo "Your score is: $(cat public/badges/$CI_JOB_NAME.score)" + artifacts: + paths: + - public + reports: + codequality: codeclimate.json + when: always + docker-build-dev: image: docker:latest services: @@ -132,7 +163,7 @@ win-dev-build: - app\classes\**\* # Download latest: # | https://gitlab.com/crafty-controller/crafty-commander/-/jobs/artifacts/dev/download?job=win-dev-build - + win-prod-build: stage: prod-deployment tags: diff --git a/.pylintrc b/.pylintrc new file mode 100644 index 00000000..d6f4103b --- /dev/null +++ b/.pylintrc @@ -0,0 +1,603 @@ +[MASTER] + +# A comma-separated list of package or module names from where C extensions may +# be loaded. Extensions are loading into the active Python interpreter and may +# run arbitrary code. +extension-pkg-allow-list= + +# A comma-separated list of package or module names from where C extensions may +# be loaded. Extensions are loading into the active Python interpreter and may +# run arbitrary code. (This is an alternative name to extension-pkg-allow-list +# for backward compatibility.) +extension-pkg-whitelist= + +# Return non-zero exit code if any of these messages/categories are detected, +# even if score is above --fail-under value. Syntax same as enable. Messages +# specified are enabled, while categories only check already-enabled messages. +fail-on= + +# Specify a score threshold to be exceeded before program exits with error. +fail-under=10.0 + +# Files or directories to be skipped. They should be base names, not paths. +ignore= + +# Add files or directories matching the regex patterns to the ignore-list. The +# regex matches against paths and can be in Posix or Windows format. +ignore-paths=app/migrations + +# Files or directories matching the regex patterns are skipped. The regex +# matches against base names, not paths. +ignore-patterns= + +# Python code to execute, usually for sys.path manipulation such as +# pygtk.require(). +#init-hook= + +# Use multiple processes to speed up Pylint. Specifying 0 will auto-detect the +# number of processors available to use. +jobs=0 + +# Control the amount of potential inferred values when inferring a single +# object. This can help the performance when dealing with large functions or +# complex, nested conditions. +limit-inference-results=100 + +# List of plugins (as comma separated values of python module names) to load, +# usually to register additional checkers. +load-plugins= + +# Pickle collected data for later comparisons. +persistent=yes + +# Minimum Python version to use for version dependent checks. Will default to +# the version used to run pylint. +py-version=3.9 + +# When enabled, pylint would attempt to guess common misconfiguration and emit +# user-friendly hints instead of false-positive error messages. +suggestion-mode=yes + +# Allow loading of arbitrary C extensions. Extensions are imported into the +# active Python interpreter and may run arbitrary code. +unsafe-load-any-extension=no + + +[MESSAGES CONTROL] + +# Only show warnings with the listed confidence levels. Leave empty to show +# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED. +confidence= + +# Disable the message, report, category or checker with the given id(s). You +# can either give multiple identifiers separated by comma (,) or put this +# option multiple times (only on the command line, not in the configuration +# file where it should appear only once). You can also use "--disable=all" to +# disable everything first and then reenable specific checks. For example, if +# you want to run only the similarities checker, you can use "--disable=all +# --enable=similarities". If you want to run only the classes checker, but have +# no Warning level messages displayed, use "--disable=all --enable=classes +# --disable=W". +disable=abstract-method, + attribute-defined-outside-init, + bad-inline-option, + bare-except, + broad-except, + cell-var-from-loop, + consider-iterating-dictionary, + consider-using-with, + deprecated-pragma, + duplicate-code, + file-ignored, + fixme, + import-error, + inconsistent-return-statements, + invalid-name, + locally-disabled, + logging-format-interpolation, + logging-fstring-interpolation, + logging-not-lazy, + missing-docstring, + no-else-break, + no-else-continue, + no-else-return, + no-self-use, + no-value-for-parameter, + not-an-iterable, + protected-access, + simplifiable-condition, + simplifiable-if-statement, + suppressed-message, + too-few-public-methods, + too-many-arguments, + too-many-branches, + too-many-instance-attributes, + too-many-locals, + too-many-nested-blocks, + too-many-public-methods, + too-many-return-statements, + too-many-statements, + use-symbolic-message-instead, + useless-suppression, + raw-checker-failed + + +# Enable the message, report, category or checker with the given id(s). You can +# either give multiple identifier separated by comma (,) or put this option +# multiple time (only on the command line, not in the configuration file where +# it should appear only once). See also the "--disable" option for examples. +enable=c-extension-no-member + + +[REPORTS] + +# Python expression which should return a score less than or equal to 10. You +# have access to the variables 'error', 'warning', 'refactor', and 'convention' +# which contain the number of messages in each category, as well as 'statement' +# which is the total number of statements analyzed. This score is used by the +# global evaluation report (RP0004). +evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) + +# Template used to display messages. This is a python new-style format string +# used to format the message information. See doc for all details. +#msg-template= + +# Set the output format. Available formats are text, parseable, colorized, json +# and msvs (visual studio). You can also give a reporter class, e.g. +# mypackage.mymodule.MyReporterClass. +output-format=text + +# Tells whether to display a full report or only the messages. +reports=no + +# Activate the evaluation score. +score=yes + + +[REFACTORING] + +# Maximum number of nested blocks for function / method body +max-nested-blocks=5 + +# Complete name of functions that never returns. When checking for +# inconsistent-return-statements if a never returning function is called then +# it will be considered as an explicit return statement and no message will be +# printed. +never-returning-functions=sys.exit,argparse.parse_error + + +[BASIC] + +# Naming style matching correct argument names. +argument-naming-style=snake_case + +# Regular expression matching correct argument names. Overrides argument- +# naming-style. +#argument-rgx= + +# Naming style matching correct attribute names. +attr-naming-style=snake_case + +# Regular expression matching correct attribute names. Overrides attr-naming- +# style. +#attr-rgx= + +# Bad variable names which should always be refused, separated by a comma. +bad-names=foo, + bar, + baz, + toto, + tutu, + tata + +# Bad variable names regexes, separated by a comma. If names match any regex, +# they will always be refused +bad-names-rgxs= + +# Naming style matching correct class attribute names. +class-attribute-naming-style=any + +# Regular expression matching correct class attribute names. Overrides class- +# attribute-naming-style. +#class-attribute-rgx= + +# Naming style matching correct class constant names. +class-const-naming-style=UPPER_CASE + +# Regular expression matching correct class constant names. Overrides class- +# const-naming-style. +#class-const-rgx= + +# Naming style matching correct class names. +class-naming-style=PascalCase + +# Regular expression matching correct class names. Overrides class-naming- +# style. +#class-rgx= + +# Naming style matching correct constant names. +const-naming-style=UPPER_CASE + +# Regular expression matching correct constant names. Overrides const-naming- +# style. +#const-rgx= + +# Minimum line length for functions/classes that require docstrings, shorter +# ones are exempt. +docstring-min-length=-1 + +# Naming style matching correct function names. +function-naming-style=snake_case + +# Regular expression matching correct function names. Overrides function- +# naming-style. +#function-rgx= + +# Good variable names which should always be accepted, separated by a comma. +good-names=i, + j, + k, + ex, + Run, + _ + +# Good variable names regexes, separated by a comma. If names match any regex, +# they will always be accepted +good-names-rgxs= + +# Include a hint for the correct naming format with invalid-name. +include-naming-hint=no + +# Naming style matching correct inline iteration names. +inlinevar-naming-style=any + +# Regular expression matching correct inline iteration names. Overrides +# inlinevar-naming-style. +#inlinevar-rgx= + +# Naming style matching correct method names. +method-naming-style=snake_case + +# Regular expression matching correct method names. Overrides method-naming- +# style. +#method-rgx= + +# Naming style matching correct module names. +module-naming-style=snake_case + +# Regular expression matching correct module names. Overrides module-naming- +# style. +#module-rgx= + +# Colon-delimited sets of names that determine each other's naming style when +# the name regexes allow several styles. +name-group= + +# Regular expression which should only match function or class names that do +# not require a docstring. +no-docstring-rgx=^_ + +# List of decorators that produce properties, such as abc.abstractproperty. Add +# to this list to register other decorators that produce valid properties. +# These decorators are taken in consideration only for invalid-name. +property-classes=abc.abstractproperty + +# Naming style matching correct variable names. +variable-naming-style=snake_case + +# Regular expression matching correct variable names. Overrides variable- +# naming-style. +#variable-rgx= + + +[FORMAT] + +# Expected format of line ending, e.g. empty (any line ending), LF or CRLF. +expected-line-ending-format= + +# Regexp for a line that is allowed to be longer than the limit. +ignore-long-lines=^\s*(# )??$ + +# Number of spaces of indent required inside a hanging or continued line. +indent-after-paren=4 + +# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 +# tab). +indent-string=' ' + +# Maximum number of characters on a single line. +max-line-length=150 + +# Maximum number of lines in a module. +max-module-lines=2000 + +# Allow the body of a class to be on the same line as the declaration if body +# contains single statement. +single-line-class-stmt=no + +# Allow the body of an if to be on the same line as the test if there is no +# else. +single-line-if-stmt=no + + +[LOGGING] + +# The type of string formatting that logging methods do. `old` means using % +# formatting, `new` is for `{}` formatting. +logging-format-style=old + +# Logging modules to check that the string format arguments are in logging +# function parameter format. +logging-modules=logging + + +[MISCELLANEOUS] + +# List of note tags to take in consideration, separated by a comma. +notes=FIXME, + XXX, + TODO + +# Regular expression of note tags to take in consideration. +#notes-rgx= + + +[SIMILARITIES] + +# Comments are removed from the similarity computation +ignore-comments=yes + +# Docstrings are removed from the similarity computation +ignore-docstrings=yes + +# Imports are removed from the similarity computation +ignore-imports=no + +# Signatures are removed from the similarity computation +ignore-signatures=no + +# Minimum lines number of a similarity. +min-similarity-lines=4 + + +[SPELLING] + +# Limits count of emitted suggestions for spelling mistakes. +max-spelling-suggestions=4 + +# Spelling dictionary name. Available dictionaries: none. To make it work, +# install the 'python-enchant' package. +spelling-dict= + +# List of comma separated words that should be considered directives if they +# appear and the beginning of a comment and should not be checked. +spelling-ignore-comment-directives=fmt: on,fmt: off,noqa:,noqa,nosec,isort:skip,mypy: + +# List of comma separated words that should not be checked. +spelling-ignore-words= + +# A path to a file that contains the private dictionary; one word per line. +spelling-private-dict-file= + +# Tells whether to store unknown words to the private dictionary (see the +# --spelling-private-dict-file option) instead of raising a message. +spelling-store-unknown-words=no + + +[STRING] + +# This flag controls whether inconsistent-quotes generates a warning when the +# character used as a quote delimiter is used inconsistently within a module. +check-quote-consistency=no + +# This flag controls whether the implicit-str-concat should generate a warning +# on implicit string concatenation in sequences defined over several lines. +check-str-concat-over-line-jumps=no + + +[TYPECHECK] + +# List of decorators that produce context managers, such as +# contextlib.contextmanager. Add to this list to register other decorators that +# produce valid context managers. +contextmanager-decorators=contextlib.contextmanager + +# List of members which are set dynamically and missed by pylint inference +# system, and so shouldn't trigger E1101 when accessed. Python regular +# expressions are accepted. +generated-members=os.* + +# Tells whether missing members accessed in mixin class should be ignored. A +# class is considered mixin if its name matches the mixin-class-rgx option. +ignore-mixin-members=yes + +# Tells whether to warn about missing members when the owner of the attribute +# is inferred to be None. +ignore-none=yes + +# This flag controls whether pylint should warn about no-member and similar +# checks whenever an opaque object is returned when inferring. The inference +# can return multiple potential results while evaluating a Python object, but +# some branches might not be evaluated, which results in partial inference. In +# that case, it might be useful to still emit no-member and other checks for +# the rest of the inferred objects. +ignore-on-opaque-inference=yes + +# List of class names for which member attributes should not be checked (useful +# for classes with dynamically set attributes). This supports the use of +# qualified names. +ignored-classes=optparse.Values,thread._local,_thread._local + +# List of module names for which member attributes should not be checked +# (useful for modules/projects where namespaces are manipulated during runtime +# and thus existing member attributes cannot be deduced by static analysis). It +# supports qualified module names, as well as Unix pattern matching. +ignored-modules= + +# Show a hint with possible names when a member name was not found. The aspect +# of finding the hint is based on edit distance. +missing-member-hint=yes + +# The minimum edit distance a name should have in order to be considered a +# similar match for a missing member name. +missing-member-hint-distance=1 + +# The total number of similar names that should be taken in consideration when +# showing a hint for a missing member. +missing-member-max-choices=1 + +# Regex pattern to define which classes are considered mixins ignore-mixin- +# members is set to 'yes' +mixin-class-rgx=.*[Mm]ixin + +# List of decorators that change the signature of a decorated function. +signature-mutators= + + +[VARIABLES] + +# List of additional names supposed to be defined in builtins. Remember that +# you should avoid defining new builtins when possible. +additional-builtins= + +# Tells whether unused global variables should be treated as a violation. +allow-global-unused-variables=yes + +# List of names allowed to shadow builtins +allowed-redefined-builtins= + +# List of strings which can identify a callback function by name. A callback +# name must start or end with one of those strings. +callbacks=cb_, + _cb + +# A regular expression matching the name of dummy variables (i.e. expected to +# not be used). +dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_ + +# Argument names that match this expression will be ignored. Default to name +# with leading underscore. +ignored-argument-names=_.*|^ignored_|^unused_ + +# Tells whether we should check for unused import in __init__ files. +init-import=no + +# List of qualified module names which can have objects that can redefine +# builtins. +redefining-builtins-modules=six.moves,past.builtins,future.builtins,builtins,io + + +[CLASSES] + +# Warn about protected attribute access inside special methods +check-protected-access-in-special-methods=no + +# List of method names used to declare (i.e. assign) instance attributes. +defining-attr-methods=__init__, + __new__, + setUp, + __post_init__ + +# List of member names, which should be excluded from the protected access +# warning. +exclude-protected=_asdict, + _fields, + _replace, + _source, + _make + +# List of valid names for the first argument in a class method. +valid-classmethod-first-arg=cls + +# List of valid names for the first argument in a metaclass class method. +valid-metaclass-classmethod-first-arg=cls + + +[DESIGN] + +# List of regular expressions of class ancestor names to ignore when counting +# public methods (see R0903) +exclude-too-few-public-methods= + +# List of qualified class names to ignore when counting class parents (see +# R0901) +ignored-parents= + +# Maximum number of arguments for function / method. +max-args=8 + +# Maximum number of attributes for a class (see R0902). +max-attributes=7 + +# Maximum number of boolean expressions in an if statement (see R0916). +max-bool-expr=5 + +# Maximum number of branch for function / method body. +max-branches=12 + +# Maximum number of locals for function / method body. +max-locals=15 + +# Maximum number of parents for a class (see R0901). +max-parents=7 + +# Maximum number of public methods for a class (see R0904). +max-public-methods=20 + +# Maximum number of return / yield for function / method body. +max-returns=6 + +# Maximum number of statements in function / method body. +max-statements=50 + +# Minimum number of public methods for a class (see R0903). +min-public-methods=2 + + +[IMPORTS] + +# List of modules that can be imported at any level, not just the top level +# one. +allow-any-import-level= + +# Allow wildcard imports from modules that define __all__. +allow-wildcard-with-all=no + +# Analyse import fallback blocks. This can be used to support both Python 2 and +# 3 compatible code, which means that the block might have code that exists +# only in one or another interpreter, leading to false positives when analysed. +analyse-fallback-blocks=no + +# Deprecated modules which should not be used, separated by a comma. +deprecated-modules= + +# Output a graph (.gv or any supported image format) of external dependencies +# to the given file (report RP0402 must not be disabled). +ext-import-graph= + +# Output a graph (.gv or any supported image format) of all (i.e. internal and +# external) dependencies to the given file (report RP0402 must not be +# disabled). +import-graph= + +# Output a graph (.gv or any supported image format) of internal dependencies +# to the given file (report RP0402 must not be disabled). +int-import-graph= + +# Force import order to recognize a module as part of the standard +# compatibility libraries. +known-standard-library= + +# Force import order to recognize a module as part of a third party library. +known-third-party=enchant + +# Couples of modules and preferred modules, separated by a comma. +preferred-modules= + + +[EXCEPTIONS] + +# Exceptions that will emit a warning when being caught. Defaults to +# "BaseException, Exception". +overgeneral-exceptions=BaseException, + Exception diff --git a/app/classes/controllers/crafty_perms_controller.py b/app/classes/controllers/crafty_perms_controller.py index b5f7f4e5..d73774c7 100644 --- a/app/classes/controllers/crafty_perms_controller.py +++ b/app/classes/controllers/crafty_perms_controller.py @@ -1,23 +1,6 @@ -import os -import time import logging -import sys -import yaml -import asyncio -import shutil -import tempfile -import zipfile -from distutils import dir_util - -from app.classes.shared.helpers import helper -from app.classes.shared.console import console from app.classes.models.crafty_permissions import crafty_permissions, Enum_Permissions_Crafty - -from app.classes.shared.server import Server -from app.classes.minecraft.server_props import ServerProps -from app.classes.minecraft.serverjars import server_jar_obj -from app.classes.minecraft.stats import Stats from app.classes.models.users import ApiKeys logger = logging.getLogger(__name__) @@ -43,15 +26,15 @@ class Crafty_Perms_Controller: return crafty_permissions.can_add_in_crafty(user_id, Enum_Permissions_Crafty.Server_Creation) @staticmethod - def can_add_user(user_id): - #TODO: Complete if we need a User Addition limit - #return crafty_permissions.can_add_in_crafty(user_id, Enum_Permissions_Crafty.User_Config) + def can_add_user(): # Add back argument 'user_id' when you work on this + #TODO: Complete if we need a User Addition limit + #return crafty_permissions.can_add_in_crafty(user_id, Enum_Permissions_Crafty.User_Config) return True @staticmethod - def can_add_role(user_id): - #TODO: Complete if we need a Role Addition limit - #return crafty_permissions.can_add_in_crafty(user_id, Enum_Permissions_Crafty.Roles_Config) + def can_add_role(): # Add back argument 'user_id' when you work on this + #TODO: Complete if we need a Role Addition limit + #return crafty_permissions.can_add_in_crafty(user_id, Enum_Permissions_Crafty.Roles_Config) return True @staticmethod diff --git a/app/classes/controllers/management_controller.py b/app/classes/controllers/management_controller.py index 4e8dacab..198588b5 100644 --- a/app/classes/controllers/management_controller.py +++ b/app/classes/controllers/management_controller.py @@ -1,25 +1,8 @@ -import os -import time import logging -import sys -import yaml -import asyncio -import shutil -import tempfile -import zipfile -from distutils import dir_util - -from app.classes.shared.helpers import helper -from app.classes.shared.console import console from app.classes.models.management import management_helper from app.classes.models.servers import servers_helper -from app.classes.shared.server import Server -from app.classes.minecraft.server_props import ServerProps -from app.classes.minecraft.serverjars import server_jar_obj -from app.classes.minecraft.stats import Stats - logger = logging.getLogger(__name__) class Management_Controller: @@ -43,14 +26,13 @@ class Management_Controller: server_name = servers_helper.get_server_friendly_name(server_id) # Example: Admin issued command start_server for server Survival - management_helper.add_to_audit_log(user_id, "issued command {} for server {}".format(command, server_name), - server_id, remote_ip) + management_helper.add_to_audit_log(user_id, f"issued command {command} for server {server_name}", server_id, remote_ip) management_helper.add_command(server_id, user_id, remote_ip, command) @staticmethod def mark_command_complete(command_id=None): return management_helper.mark_command_complete(command_id) - + #************************************************************************************************ # Audit_Log Methods #************************************************************************************************ @@ -71,7 +53,16 @@ class Management_Controller: #************************************************************************************************ @staticmethod def create_scheduled_task(server_id, action, interval, interval_type, start_time, command, comment=None, enabled=True): - return management_helper.create_scheduled_task(server_id, action, interval, interval_type, start_time, command, comment, enabled) + return management_helper.create_scheduled_task( + server_id, + action, + interval, + interval_type, + start_time, + command, + comment, + enabled + ) @staticmethod def delete_scheduled_task(schedule_id): diff --git a/app/classes/controllers/roles_controller.py b/app/classes/controllers/roles_controller.py index aa1e936d..4071ba7e 100644 --- a/app/classes/controllers/roles_controller.py +++ b/app/classes/controllers/roles_controller.py @@ -1,25 +1,10 @@ -import os -import time import logging -import sys -import yaml -import asyncio -import shutil -import tempfile -import zipfile -from distutils import dir_util - -from app.classes.shared.helpers import helper -from app.classes.shared.console import console from app.classes.models.roles import roles_helper from app.classes.models.server_permissions import server_permissions from app.classes.models.users import users_helper -from app.classes.shared.server import Server -from app.classes.minecraft.server_props import ServerProps -from app.classes.minecraft.serverjars import server_jar_obj -from app.classes.minecraft.stats import Stats +from app.classes.shared.helpers import helper logger = logging.getLogger(__name__) @@ -45,7 +30,6 @@ class Roles_Controller: base_data = Roles_Controller.get_role_with_servers(role_id) up_data = {} added_servers = set() - edited_servers = set() removed_servers = set() for key in role_data: if key == "role_id": @@ -56,7 +40,7 @@ class Roles_Controller: elif base_data[key] != role_data[key]: up_data[key] = role_data[key] up_data['last_update'] = helper.get_time_as_string() - logger.debug("role: {} +server:{} -server{}".format(role_data, added_servers, removed_servers)) + logger.debug(f"role: {role_data} +server:{added_servers} -server{removed_servers}") for server in added_servers: server_permissions.get_or_create(role_id, server, permissions_mask) for server in base_data['servers']: @@ -96,4 +80,4 @@ class Roles_Controller: return role else: #logger.debug("role: ({}) {}".format(role_id, {})) - return {} \ No newline at end of file + return {} diff --git a/app/classes/controllers/server_perms_controller.py b/app/classes/controllers/server_perms_controller.py index 9f49f324..9afa5bd2 100644 --- a/app/classes/controllers/server_perms_controller.py +++ b/app/classes/controllers/server_perms_controller.py @@ -1,32 +1,20 @@ -import os -import time import logging -import sys -import yaml -import asyncio -import shutil -import tempfile -import zipfile -from distutils import dir_util -from app.classes.shared.helpers import helper -from app.classes.shared.console import console - -from app.classes.shared.main_models import db_helper from app.classes.models.server_permissions import server_permissions, Enum_Permissions_Server from app.classes.models.users import users_helper, ApiKeys from app.classes.models.roles import roles_helper from app.classes.models.servers import servers_helper -from app.classes.shared.server import Server -from app.classes.minecraft.server_props import ServerProps -from app.classes.minecraft.serverjars import server_jar_obj -from app.classes.minecraft.stats import Stats +from app.classes.shared.main_models import db_helper logger = logging.getLogger(__name__) class Server_Perms_Controller: + @staticmethod + def get_server_user_list(server_id): + return server_permissions.get_server_user_list(server_id) + @staticmethod def list_defined_permissions(): permissions_list = server_permissions.get_permissions_list() @@ -54,7 +42,9 @@ class Server_Perms_Controller: def backup_role_swap(old_server_id, new_server_id): role_list = server_permissions.get_server_roles(old_server_id) for role in role_list: - server_permissions.add_role_server(new_server_id, role.role_id, server_permissions.get_permissions_mask(int(role.role_id), int(old_server_id))) + server_permissions.add_role_server( + new_server_id, role.role_id, + server_permissions.get_permissions_mask(int(role.role_id), int(old_server_id))) #server_permissions.add_role_server(new_server_id, role.role_id, '00001000') #************************************************************************************************ @@ -85,19 +75,6 @@ class Server_Perms_Controller: def get_api_key_permissions_list(key: ApiKeys, server_id: str): return server_permissions.get_api_key_permissions_list(key, server_id) - @staticmethod - def get_user_id_permissions_list(user_id: str, server_id: str): - return server_permissions.get_user_id_permissions_list(user_id, server_id) - - @staticmethod - def get_api_key_id_permissions_list(key_id: str, server_id: str): - key = users_helper.get_user_api_key(key_id) - return server_permissions.get_api_key_permissions_list(key, server_id) - - @staticmethod - def get_api_key_permissions_list(key: ApiKeys, server_id: str): - return server_permissions.get_api_key_permissions_list(key, server_id) - @staticmethod def get_authorized_servers_stats_from_roles(user_id): user_roles = users_helper.get_user_roles_id(user_id) diff --git a/app/classes/controllers/servers_controller.py b/app/classes/controllers/servers_controller.py index c2ed77a9..24b19cf7 100644 --- a/app/classes/controllers/servers_controller.py +++ b/app/classes/controllers/servers_controller.py @@ -1,29 +1,15 @@ -from app.classes.controllers.roles_controller import Roles_Controller import os -import time import logging import json -import sys -import yaml -import asyncio -import shutil -import tempfile -import zipfile -from distutils import dir_util -from app.classes.shared.helpers import helper -from app.classes.shared.console import console - -from app.classes.shared.main_models import db_helper from app.classes.models.servers import servers_helper -from app.classes.models.roles import roles_helper from app.classes.models.users import users_helper, ApiKeys from app.classes.models.server_permissions import server_permissions, Enum_Permissions_Server -from app.classes.shared.server import Server -from app.classes.minecraft.server_props import ServerProps -from app.classes.minecraft.serverjars import server_jar_obj -from app.classes.minecraft.stats import Stats +from app.classes.shared.helpers import helper +from app.classes.shared.main_models import db_helper + +from app.classes.controllers.roles_controller import Roles_Controller logger = logging.getLogger(__name__) @@ -33,8 +19,26 @@ class Servers_Controller: # Generic Servers Methods #************************************************************************************************ @staticmethod - def create_server(name: str, server_uuid: str, server_dir: str, backup_path: str, server_command: str, server_file: str, server_log_file: str, server_stop: str, server_port=25565): - return servers_helper.create_server(name, server_uuid, server_dir, backup_path, server_command, server_file, server_log_file, server_stop, server_port) + def create_server( + name: str, + server_uuid: str, + server_dir: str, + backup_path: str, + server_command: str, + server_file: str, + server_log_file: str, + server_stop: str, + server_port=25565): + return servers_helper.create_server( + name, + server_uuid, + server_dir, + backup_path, + server_command, + server_file, + server_log_file, + server_stop, + server_port) @staticmethod def get_server_obj(server_id): @@ -183,7 +187,7 @@ class Servers_Controller: path = os.path.join(server_path, 'banned-players.json') try: - with open(helper.get_os_understandable_path(path)) as file: + with open(helper.get_os_understandable_path(path), encoding='utf-8') as file: content = file.read() file.close() except Exception as ex: @@ -210,4 +214,3 @@ class Servers_Controller: if helper.check_file_exists(log_file_path) and \ helper.is_file_older_than_x_days(log_file_path, logs_delete_after): os.remove(log_file_path) - diff --git a/app/classes/controllers/users_controller.py b/app/classes/controllers/users_controller.py index 399da361..12bb4c2c 100644 --- a/app/classes/controllers/users_controller.py +++ b/app/classes/controllers/users_controller.py @@ -1,28 +1,16 @@ -import os -import time import logging -import sys from typing import Optional -import yaml -import asyncio -import shutil -import tempfile -import zipfile -from distutils import dir_util - from app.classes.shared.helpers import helper -from app.classes.shared.console import console - -from app.classes.models.users import Users, users_helper from app.classes.shared.authentication import authentication + +from app.classes.models.users import users_helper from app.classes.models.crafty_permissions import crafty_permissions, Enum_Permissions_Crafty -from app.classes.models.management import management_helper logger = logging.getLogger(__name__) class Users_Controller: - + #************************************************************************************************ # Users Methods #************************************************************************************************ @@ -60,7 +48,6 @@ class Users_Controller: up_data = {} added_roles = set() removed_roles = set() - removed_servers = set() for key in user_data: if key == "user_id": continue @@ -74,7 +61,7 @@ class Users_Controller: up_data[key] = user_data[key] up_data['last_update'] = helper.get_time_as_string() up_data['lang'] = user_data['lang'] - logger.debug("user: {} +role:{} -role:{}".format(user_data, added_roles, removed_roles)) + logger.debug(f"user: {user_data} +role:{added_roles} -role:{removed_roles}") for role in added_roles: users_helper.get_or_create(user_id=user_id, role_id=role) permissions_mask = user_crafty_data.get('permissions_mask', '000') @@ -90,9 +77,14 @@ class Users_Controller: limit_user_creation = 0 limit_role_creation = 0 - crafty_permissions.add_or_update_user(user_id, permissions_mask, limit_server_creation, limit_user_creation, limit_role_creation) + crafty_permissions.add_or_update_user( + user_id, + permissions_mask, + limit_server_creation, + limit_user_creation, + limit_role_creation) - users_helper.delete_user_roles(user_id, removed_roles) + users_helper.delete_user_roles(user_id, removed_roles) users_helper.update_user(user_id, up_data) @@ -121,7 +113,7 @@ class Users_Controller: # ************************************************************************************************ # User Roles Methods # ************************************************************************************************ - + @staticmethod def get_user_roles_id(user_id): return users_helper.get_user_roles_id(user_id) @@ -137,7 +129,7 @@ class Users_Controller: @staticmethod def add_user_roles(user): return users_helper.add_user_roles(user) - + @staticmethod def user_role_query(user_id): return users_helper.user_role_query(user_id) diff --git a/app/classes/minecraft/mc_ping.py b/app/classes/minecraft/mc_ping.py index 0d21d9d4..52d3cdec 100644 --- a/app/classes/minecraft/mc_ping.py +++ b/app/classes/minecraft/mc_ping.py @@ -1,11 +1,10 @@ -from app.classes.shared.helpers import Helpers import struct import socket import base64 import json -import sys import os import logging.config + from app.classes.shared.console import console logger = logging.getLogger(__name__) @@ -41,7 +40,7 @@ class Server: if "obfuscated" in e.keys(): lines.append(get_code_format("obfuscated")) if "color" in e.keys(): - lines.append(get_code_format(e['color'])) + lines.append(get_code_format(e['color'])) #Then append the text if "text" in e.keys(): if e['text'] == '\n': @@ -101,13 +100,13 @@ def get_code_format(format_name): if format_name in data.keys(): return data.get(format_name) else: - logger.error("Format MOTD Error: format name {} does not exist".format(format_name)) - console.error("Format MOTD Error: format name {} does not exist".format(format_name)) + 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("Config File Error: Unable to read {} due to {}".format(format_file, e)) - console.critical("Config File Error: Unable to read {} due to {}".format(format_file, e)) + logger.critical(f"Config File Error: Unable to read {format_file} due to {e}") + console.critical(f"Config File Error: Unable to read {format_file} due to {e}") return "" @@ -126,15 +125,14 @@ def ping(ip, port): j += 1 if j > 5: raise ValueError('var_int too big') - if not (k & 0x80): + if not k & 0x80: return i sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) try: sock.connect((ip, port)) - except socket.error as err: - pass + except socket.error: return False try: @@ -163,7 +161,7 @@ def ping(ip, port): return False data += chunk - logger.debug("Server reports this data on ping: {}".format(data)) + logger.debug(f"Server reports this data on ping: {data}") return Server(json.loads(data)) finally: sock.close() diff --git a/app/classes/minecraft/server_props.py b/app/classes/minecraft/server_props.py index 356fac67..027e11ac 100644 --- a/app/classes/minecraft/server_props.py +++ b/app/classes/minecraft/server_props.py @@ -9,7 +9,7 @@ class ServerProps: def _parse(self): """Loads and parses the file specified in self.filepath""" - with open(self.filepath) as fp: + with open(self.filepath, encoding='utf-8') as fp: line = fp.readline() d = {} if os.path.exists(".header"): @@ -24,7 +24,7 @@ class ServerProps: s2 = s[s.find('=')+1:] d[s1] = s2 else: - with open(".header", "a+") as h: + with open(".header", "a+", encoding='utf-8') as h: h.write(line) line = fp.readline() return d @@ -47,9 +47,9 @@ class ServerProps: def save(self): """Writes to the new file""" - with open(self.filepath, "a+") as f: + with open(self.filepath, "a+", encoding='utf-8') as f: f.truncate(0) - with open(".header") as header: + with open(".header", encoding='utf-8') as header: line = header.readline() while line: f.write(line) diff --git a/app/classes/minecraft/serverjars.py b/app/classes/minecraft/serverjars.py index 386268c5..95e013aa 100644 --- a/app/classes/minecraft/serverjars.py +++ b/app/classes/minecraft/serverjars.py @@ -1,4 +1,3 @@ -import os import sys import json import threading @@ -9,19 +8,15 @@ from datetime import datetime from app.classes.shared.helpers import helper from app.classes.shared.console import console -from app.classes.models.servers import Servers -from app.classes.minecraft.server_props import ServerProps -from app.classes.web.websocket_helper import websocket_helper -from app.classes.models.server_permissions import server_permissions logger = logging.getLogger(__name__) try: import requests -except ModuleNotFoundError as e: - logger.critical("Import Error: Unable to load {} module".format(e.name), exc_info=True) - console.critical("Import Error: Unable to load {} module".format(e.name)) +except ModuleNotFoundError as err: + logger.critical(f"Import Error: Unable to load {err.name} module", exc_info=True) + console.critical(f"Import Error: Unable to load {err.name} module") sys.exit(1) @@ -31,7 +26,7 @@ class ServerJars: self.base_url = "https://serverjars.com" def _get_api_result(self, call_url: str): - full_url = "{base}{call_url}".format(base=self.base_url, call_url=call_url) + full_url = f"{self.base_url}{call_url}" try: r = requests.get(full_url, timeout=2) @@ -39,20 +34,20 @@ class ServerJars: if r.status_code not in [200, 201]: return {} except Exception as e: - logger.error("Unable to connect to serverjar.com api due to error: {}".format(e)) + logger.error(f"Unable to connect to serverjar.com api due to error: {e}") return {} try: api_data = json.loads(r.content) except Exception as e: - logger.error("Unable to parse serverjar.com api result due to error: {}".format(e)) + logger.error(f"Unable to parse serverjar.com api result due to error: {e}") return {} api_result = api_data.get('status') api_response = api_data.get('response', {}) if api_result != "success": - logger.error("Api returned a failed status: {}".format(api_result)) + logger.error(f"Api returned a failed status: {api_result}") return {} return api_response @@ -62,11 +57,11 @@ class ServerJars: cache_file = helper.serverjar_cache cache = {} try: - with open(cache_file, "r") as f: + with open(cache_file, "r", encoding='utf-8') as f: cache = json.load(f) except Exception as e: - logger.error("Unable to read serverjars.com cache file: {}".format(e)) + logger.error(f"Unable to read serverjars.com cache file: {e}") return cache @@ -100,7 +95,7 @@ class ServerJars: def _check_api_alive(self): logger.info("Checking serverjars.com API status") - check_url = "{base}/api/fetchTypes".format(base=self.base_url) + check_url = f"{self.base_url}/api/fetchTypes" try: r = requests.get(check_url, timeout=2) @@ -108,7 +103,7 @@ class ServerJars: logger.info("Serverjars.com API is alive") return True except Exception as e: - logger.error("Unable to connect to serverjar.com api due to error: {}".format(e)) + logger.error(f"Unable to connect to serverjar.com api due to error: {e}") return {} logger.error("unable to contact serverjars.com api") @@ -154,15 +149,15 @@ class ServerJars: # save our cache try: - with open(cache_file, "w") as f: + with open(cache_file, "w", encoding='utf-8') as f: f.write(json.dumps(data, indent=4)) logger.info("Cache file refreshed") except Exception as e: - logger.error("Unable to update serverjars.com cache file: {}".format(e)) + logger.error(f"Unable to update serverjars.com cache file: {e}") def _get_jar_details(self, jar_type='servers'): - url = '/api/fetchAll/{type}'.format(type=jar_type) + url = f'/api/fetchAll/{jar_type}' response = self._get_api_result(url) temp = [] for v in response: @@ -175,12 +170,12 @@ class ServerJars: response = self._get_api_result(url) return response - def download_jar(self, server, version, path, name): - update_thread = threading.Thread(target=self.a_download_jar, daemon=True, name="exe_download", args=(server, version, path, name)) + def download_jar(self, server, version, path): + update_thread = threading.Thread(target=self.a_download_jar, daemon=True, args=(server, version, path)) update_thread.start() - def a_download_jar(self, server, version, path, name): - fetch_url = "{base}/api/fetchJar/{server}/{version}".format(base=self.base_url, server=server, version=version) + def a_download_jar(self, server, version, path): + fetch_url = f"{self.base_url}/api/fetchJar/{server}/{version}" # open a file stream with requests.get(fetch_url, timeout=2, stream=True) as r: @@ -189,9 +184,7 @@ class ServerJars: shutil.copyfileobj(r.raw, output) except Exception as e: - logger.error("Unable to save jar to {path} due to error:{error}".format(path=path, error=e)) - pass - + logger.error(f"Unable to save jar to {path} due to error:{e}") return False diff --git a/app/classes/minecraft/stats.py b/app/classes/minecraft/stats.py index ee632cf6..458c9532 100644 --- a/app/classes/minecraft/stats.py +++ b/app/classes/minecraft/stats.py @@ -1,16 +1,15 @@ import os import json -import time -import psutil import logging import datetime import base64 +import psutil +from app.classes.models.management import Host_Stats +from app.classes.models.servers import Server_Stats, servers_helper from app.classes.shared.helpers import helper from app.classes.minecraft.mc_ping import ping -from app.classes.models.management import Host_Stats -from app.classes.models.servers import Server_Stats, servers_helper logger = logging.getLogger(__name__) @@ -64,8 +63,6 @@ class Stats: real_cpu = round(p.cpu_percent(interval=0.5) / psutil.cpu_count(), 2) - process_start_time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(p.create_time())) - # this is a faster way of getting data for a process with p.oneshot(): process_stats = { @@ -76,7 +73,7 @@ class Stats: return process_stats except Exception as e: - logger.error("Unable to get process details for pid: {} due to error: {}".format(process_pid, e)) + logger.error(f"Unable to get process details for pid: {process_pid} due to error: {e}") # Dummy Data process_stats = { @@ -119,13 +116,13 @@ class Stats: total_size = 0 # do a scan of the directories in the server path. - for root, dirs, files in os.walk(world_path, topdown=False): + for root, dirs, _files in os.walk(world_path, topdown=False): # for each directory we find for name in dirs: # if the directory name is "region" - if name == "region": + if str(name) == "region": # log it! logger.debug("Path %s is called region. Getting directory size", os.path.join(root, name)) @@ -144,14 +141,14 @@ class Stats: online_stats = json.loads(ping_obj.players) except Exception as e: - logger.info("Unable to read json from ping_obj: {}".format(e)) - pass + logger.info(f"Unable to read json from ping_obj: {e}") + try: server_icon = base64.encodebytes(ping_obj.icon) except Exception as e: server_icon = False - logger.info("Unable to read the server icon : {}".format(e)) + logger.info(f"Unable to read the server icon : {e}") ping_data = { 'online': online_stats.get("online", 0), @@ -168,18 +165,18 @@ class Stats: server = servers_helper.get_server_data_by_id(server_id) - logger.info("Getting players for server {}".format(server)) + logger.info(f"Getting players for server {server}") # get our settings and data dictionaries - server_settings = server.get('server_settings', {}) - server_data = server.get('server_data_obj', {}) + # server_settings = server.get('server_settings', {}) + # server_data = server.get('server_data_obj', {}) # TODO: search server properties file for possible override of 127.0.0.1 internal_ip = server['server_ip'] server_port = server['server_port'] - logger.debug("Pinging {} on port {}".format(internal_ip, server_port)) + logger.debug("Pinging {internal_ip} on port {server_port}") int_mc_ping = ping(internal_ip, int(server_port)) ping_data = {} @@ -205,7 +202,7 @@ class Stats: server = servers_helper.get_server_data_by_id(server_id) - logger.debug('Getting stats for server: {}'.format(server_id)) + logger.debug(f'Getting stats for server: {server_id}') # get our server object, settings and data dictionaries server_obj = s.get('server_obj', None) @@ -223,8 +220,9 @@ class Stats: # TODO: search server properties file for possible override of 127.0.0.1 internal_ip = server['server_ip'] server_port = server['server_port'] + server = s.get('server_name', f"ID#{server_id}") - logger.debug("Pinging server '{}' on {}:{}".format(s.get('server_name', "ID#{}".format(server_id)), internal_ip, server_port)) + logger.debug("Pinging server '{server}' on {internal_ip}:{server_port}") int_mc_ping = ping(internal_ip, int(server_port)) int_data = False @@ -263,7 +261,7 @@ class Stats: server_stats = {} server = self.controller.get_server_obj(server_id) - logger.debug('Getting stats for server: {}'.format(server_id)) + logger.debug(f'Getting stats for server: {server_id}') # get our server object, settings and data dictionaries server_obj = self.controller.get_server_obj(server_id) @@ -285,7 +283,7 @@ class Stats: server_port = server_settings.get('server-port', "25565") - logger.debug("Pinging server '{}' on {}:{}".format(server.name, internal_ip, server_port)) + logger.debug(f"Pinging server '{server.name}' on {internal_ip}:{server_port}") int_mc_ping = ping(internal_ip, int(server_port)) int_data = False @@ -360,4 +358,4 @@ class Stats: last_week = now.day - max_age Host_Stats.delete().where(Host_Stats.time < last_week).execute() - Server_Stats.delete().where(Server_Stats.created < last_week).execute() \ No newline at end of file + Server_Stats.delete().where(Server_Stats.created < last_week).execute() diff --git a/app/classes/models/crafty_permissions.py b/app/classes/models/crafty_permissions.py index c0bc9c6b..0a3644ed 100644 --- a/app/classes/models/crafty_permissions.py +++ b/app/classes/models/crafty_permissions.py @@ -1,26 +1,23 @@ -import os import sys import logging -import datetime from app.classes.shared.helpers import helper from app.classes.shared.console import console -from app.classes.models.users import Users, ApiKeys from app.classes.shared.permission_helper import permission_helper +from app.classes.models.users import Users, ApiKeys + logger = logging.getLogger(__name__) peewee_logger = logging.getLogger('peewee') peewee_logger.setLevel(logging.INFO) try: - from peewee import * - from playhouse.shortcuts import model_to_dict + from peewee import SqliteDatabase, Model, ForeignKeyField, CharField, IntegerField, DoesNotExist from enum import Enum - import yaml except ModuleNotFoundError as e: - logger.critical("Import Error: Unable to load {} module".format(e.name), exc_info=True) - console.critical("Import Error: Unable to load {} module".format(e.name)) + logger.critical(f"Import Error: Unable to load {e.name} module", exc_info=True) + console.critical(f"Import Error: Unable to load {e.name} module") sys.exit(1) database = SqliteDatabase(helper.db_path, pragmas={ @@ -124,7 +121,7 @@ class Permissions_Crafty: def get_User_Crafty(user_id): try: user_crafty = User_Crafty.select().where(User_Crafty.user_id == user_id).get() - except User_Crafty.DoesNotExist: + except DoesNotExist: user_crafty = User_Crafty.insert({ User_Crafty.user_id: user_id, User_Crafty.permissions: "000", @@ -173,7 +170,6 @@ class Permissions_Crafty: @staticmethod def get_crafty_limit_value(user_id, permission): - user_crafty = crafty_permissions.get_User_Crafty(user_id) quantity_list = crafty_permissions.get_permission_quantity_list(user_id) return quantity_list[permission] @@ -206,4 +202,4 @@ class Permissions_Crafty: -crafty_permissions = Permissions_Crafty() \ No newline at end of file +crafty_permissions = Permissions_Crafty() diff --git a/app/classes/models/management.py b/app/classes/models/management.py index 19508aa0..bd47f0c4 100644 --- a/app/classes/models/management.py +++ b/app/classes/models/management.py @@ -1,4 +1,3 @@ -import os import sys import logging import datetime @@ -6,26 +5,24 @@ import datetime from app.classes.shared.helpers import helper from app.classes.shared.console import console from app.classes.shared.main_models import db_helper -from app.classes.models.users import Users, users_helper -from app.classes.models.servers import Servers, servers_helper -from app.classes.web.websocket_helper import websocket_helper -from app.classes.models.server_permissions import server_permissions -import time +from app.classes.models.users import Users, users_helper +from app.classes.models.servers import Servers +from app.classes.models.server_permissions import server_permissions + +from app.classes.web.websocket_helper import websocket_helper logger = logging.getLogger(__name__) peewee_logger = logging.getLogger('peewee') peewee_logger.setLevel(logging.INFO) try: - from peewee import * + from peewee import SqliteDatabase, Model, ForeignKeyField, CharField, IntegerField, DateTimeField, FloatField, TextField, AutoField, BooleanField from playhouse.shortcuts import model_to_dict - from enum import Enum - import yaml except ModuleNotFoundError as e: - logger.critical("Import Error: Unable to load {} module".format(e.name), exc_info=True) - console.critical("Import Error: Unable to load {} module".format(e.name)) + logger.critical(f"Import Error: Unable to load {e.name} module", exc_info=True) + console.critical(f"Import Error: Unable to load {e.name} module") sys.exit(1) database = SqliteDatabase(helper.db_path, pragmas={ @@ -140,7 +137,7 @@ class helpers_management: #************************************************************************************************ @staticmethod def get_latest_hosts_stats(): - query = Host_Stats.select().order_by(Host_Stats.id.desc()).get() + query = Host_Stats.select().order_by(Host_Stats).get() return model_to_dict(query) #************************************************************************************************ @@ -163,7 +160,7 @@ class helpers_management: @staticmethod def mark_command_complete(command_id=None): if command_id is not None: - logger.debug("Marking Command {} completed".format(command_id)) + logger.debug(f"Marking Command {command_id} completed") Commands.update({ Commands.executed: True }).where(Commands.command_id == command_id).execute() @@ -178,10 +175,10 @@ class helpers_management: @staticmethod def add_to_audit_log(user_id, log_msg, server_id=None, source_ip=None): - logger.debug("Adding to audit log User:{} - Message: {} ".format(user_id, log_msg)) + logger.debug(f"Adding to audit log User:{user_id} - Message: {log_msg} ") user_data = users_helper.get_user(user_id) - audit_msg = "{} {}".format(str(user_data['username']).capitalize(), log_msg) + audit_msg = f"{str(user_data['username']).capitalize()} {log_msg}" server_users = server_permissions.get_server_user_list(server_id) for user in server_users: @@ -209,7 +206,17 @@ class helpers_management: # Schedules Methods #************************************************************************************************ @staticmethod - def create_scheduled_task(server_id, action, interval, interval_type, start_time, command, comment=None, enabled=True, one_time=False, cron_string='* * * * *'): + def create_scheduled_task( + server_id, + action, + interval, + interval_type, + start_time, + command, + comment=None, + enabled=True, + one_time=False, + cron_string='* * * * *'): sch_id = Schedules.insert({ Schedules.server_id: server_id, Schedules.action: action, @@ -236,11 +243,11 @@ class helpers_management: @staticmethod def delete_scheduled_task_by_server(server_id): - Schedules.delete().where(Schedules.server_id == int(server_id)).execute() + Schedules.delete().where(Schedules.server_id == int(server_id)).execute() @staticmethod def get_scheduled_task(schedule_id): - return model_to_dict(Schedules.get(Schedules.schedule_id == schedule_id)).execute() + return model_to_dict(Schedules.get(Schedules.schedule_id == schedule_id)) @staticmethod def get_scheduled_task_model(schedule_id): @@ -256,7 +263,7 @@ class helpers_management: @staticmethod def get_schedules_enabled(): - return Schedules.select().where(Schedules.enabled == True).execute() + return Schedules.select().where(Schedules.enabled is True).execute() #************************************************************************************************ # Backups Methods @@ -282,12 +289,10 @@ class helpers_management: @staticmethod def set_backup_config(server_id: int, backup_path: str = None, max_backups: int = None): - logger.debug("Updating server {} backup config with {}".format(server_id, locals())) + logger.debug(f"Updating server {server_id} backup config with {locals()}") try: - row = Backups.select().where(Backups.server_id == server_id).join(Servers)[0] new_row = False conf = {} - schd = {} except IndexError: conf = { "directories": None, @@ -304,13 +309,13 @@ class helpers_management: else: u1 = 0 u2 = Backups.update(conf).where(Backups.server_id == server_id).execute() - logger.debug("Updating existing backup record. {}+{} rows affected".format(u1, u2)) + logger.debug(f"Updating existing backup record. {u1}+{u2} rows affected") else: with database.atomic(): conf["server_id"] = server_id if backup_path is not None: - u = Servers.update(backup_path=backup_path).where(Servers.server_id == server_id) - b = Backups.create(**conf) + Servers.update(backup_path=backup_path).where(Servers.server_id == server_id) + Backups.create(**conf) logger.debug("Creating new backup record.") diff --git a/app/classes/models/roles.py b/app/classes/models/roles.py index 49146b93..ddecd798 100644 --- a/app/classes/models/roles.py +++ b/app/classes/models/roles.py @@ -1,4 +1,3 @@ -import os import sys import logging import datetime @@ -11,14 +10,12 @@ peewee_logger = logging.getLogger('peewee') peewee_logger.setLevel(logging.INFO) try: - from peewee import * + from peewee import SqliteDatabase, Model, CharField, DoesNotExist, AutoField, DateTimeField from playhouse.shortcuts import model_to_dict - from enum import Enum - import yaml except ModuleNotFoundError as e: - logger.critical("Import Error: Unable to load {} module".format(e.name), exc_info=True) - console.critical("Import Error: Unable to load {} module".format(e.name)) + logger.critical(f"Import Error: Unable to load {e.name} module", exc_info=True) + console.critical(f"Import Error: Unable to load {e.name} module") sys.exit(1) database = SqliteDatabase(helper.db_path, pragmas={ @@ -45,7 +42,7 @@ class helper_roles: @staticmethod def get_all_roles(): query = Roles.select() - return query + return query @staticmethod def get_roleid_by_name(role_name): @@ -81,5 +78,5 @@ class helper_roles: if not roles_helper.get_role(role_id): return False return True - + roles_helper = helper_roles() diff --git a/app/classes/models/server_permissions.py b/app/classes/models/server_permissions.py index 5d150187..b666deba 100644 --- a/app/classes/models/server_permissions.py +++ b/app/classes/models/server_permissions.py @@ -1,28 +1,26 @@ -import os import sys import logging -import datetime from app.classes.shared.helpers import helper from app.classes.shared.console import console +from app.classes.shared.permission_helper import permission_helper + from app.classes.models.servers import Servers from app.classes.models.roles import Roles from app.classes.models.users import User_Roles, users_helper, ApiKeys, Users -from app.classes.shared.permission_helper import permission_helper + logger = logging.getLogger(__name__) peewee_logger = logging.getLogger('peewee') peewee_logger.setLevel(logging.INFO) try: - from peewee import * - from playhouse.shortcuts import model_to_dict + from peewee import SqliteDatabase, Model, ForeignKeyField, CharField, CompositeKey, JOIN from enum import Enum - import yaml except ModuleNotFoundError as e: - logger.critical("Import Error: Unable to load {} module".format(e.name), exc_info=True) - console.critical("Import Error: Unable to load {} module".format(e.name)) + logger.critical(f"Import Error: Unable to load {e.name} module", exc_info=True) + console.critical(f"Import Error: Unable to load {e.name} module") sys.exit(1) database = SqliteDatabase(helper.db_path, pragmas={ @@ -118,7 +116,8 @@ class Permissions_Servers: @staticmethod def add_role_server(server_id, role_id, rs_permissions="00000000"): - servers = Role_Servers.insert({Role_Servers.server_id: server_id, Role_Servers.role_id: role_id, Role_Servers.permissions: rs_permissions}).execute() + servers = Role_Servers.insert({Role_Servers.server_id: server_id, Role_Servers.role_id: role_id, + Role_Servers.permissions: rs_permissions}).execute() return servers @staticmethod @@ -181,12 +180,13 @@ class Permissions_Servers: def get_server_user_list(server_id): final_users = [] server_roles = Role_Servers.select().where(Role_Servers.server_id == server_id) + # pylint: disable=singleton-comparison super_users = Users.select().where(Users.superuser == True) for role in server_roles: users = User_Roles.select().where(User_Roles.role_id == role.role_id) for user in users: if user.user_id.user_id not in final_users: - final_users.append(user.user_id.user_id) + final_users.append(user.user_id.user_id) for suser in super_users: if suser.user_id not in final_users: final_users.append(suser.user_id) diff --git a/app/classes/models/servers.py b/app/classes/models/servers.py index 0302efd1..d29e903e 100644 --- a/app/classes/models/servers.py +++ b/app/classes/models/servers.py @@ -1,11 +1,9 @@ -import os import sys import logging import datetime from app.classes.shared.helpers import helper from app.classes.shared.console import console - from app.classes.shared.main_models import db_helper logger = logging.getLogger(__name__) @@ -13,14 +11,11 @@ peewee_logger = logging.getLogger('peewee') peewee_logger.setLevel(logging.INFO) try: - from peewee import * - from playhouse.shortcuts import model_to_dict - from enum import Enum - import yaml + from peewee import SqliteDatabase, Model, ForeignKeyField, CharField, AutoField, DateTimeField, BooleanField, IntegerField, FloatField except ModuleNotFoundError as e: - logger.critical("Import Error: Unable to load {} module".format(e.name), exc_info=True) - console.critical("Import Error: Unable to load {} module".format(e.name)) + logger.critical(f"Import Error: Unable to load {e.name} module", exc_info=True) + console.critical(f"Import Error: Unable to load {e.name} module") sys.exit(1) database = SqliteDatabase(helper.db_path, pragmas={ @@ -94,7 +89,16 @@ class helper_servers: # Generic Servers Methods #************************************************************************************************ @staticmethod - def create_server(name: str, server_uuid: str, server_dir: str, backup_path: str, server_command: str, server_file: str, server_log_file: str, server_stop: str, server_port=25565): + def create_server( + name: str, + server_uuid: str, + server_dir: str, + backup_path: str, + server_command: str, + server_file: str, + server_log_file: str, + server_stop: str, + server_port=25565): return Servers.insert({ Servers.server_name: name, Servers.server_uuid: server_uuid, @@ -153,7 +157,7 @@ class helper_servers: @staticmethod def get_server_friendly_name(server_id): server_data = servers_helper.get_server_data_by_id(server_id) - friendly_name = "{} with ID: {}".format(server_data.get('server_name', None), server_data.get('server_id', 0)) + friendly_name = f"{server_data.get('server_name', None)} with ID: {server_data.get('server_id', 0)}" return friendly_name #************************************************************************************************ @@ -177,9 +181,10 @@ class helper_servers: @staticmethod def set_update(server_id, value): try: - row = Server_Stats.select().where(Server_Stats.server_id == server_id) + #Checks if server even exists + Server_Stats.select().where(Server_Stats.server_id == server_id) except Exception as ex: - logger.error("Database entry not found. ".format(ex)) + logger.error(f"Database entry not found! {ex}") with database.atomic(): Server_Stats.update(updating=value).where(Server_Stats.server_id == server_id).execute() @@ -192,9 +197,11 @@ class helper_servers: def set_first_run(server_id): #Sets first run to false try: - row = Server_Stats.select().where(Server_Stats.server_id == server_id) + #Checks if server even exists + Server_Stats.select().where(Server_Stats.server_id == server_id) except Exception as ex: - logger.error("Database entry not found. ".format(ex)) + logger.error(f"Database entry not found! {ex}") + return with database.atomic(): Server_Stats.update(first_run=False).where(Server_Stats.server_id == server_id).execute() @@ -206,7 +213,12 @@ class helper_servers: @staticmethod def get_TTL_without_player(server_id): last_stat = Server_Stats.select().where(Server_Stats.server_id == server_id).order_by(Server_Stats.created.desc()).first() - last_stat_with_player = Server_Stats.select().where(Server_Stats.server_id == server_id).where(Server_Stats.online > 0).order_by(Server_Stats.created.desc()).first() + last_stat_with_player = (Server_Stats + .select() + .where(Server_Stats.server_id == server_id) + .where(Server_Stats.online > 0) + .order_by(Server_Stats.created.desc()) + .first()) return last_stat.created - last_stat_with_player.created @staticmethod @@ -220,9 +232,10 @@ class helper_servers: @staticmethod def set_waiting_start(server_id, value): try: - row = Server_Stats.select().where(Server_Stats.server_id == server_id) + # Checks if server even exists + Server_Stats.select().where(Server_Stats.server_id == server_id) except Exception as ex: - logger.error("Database entry not found. ".format(ex)) + logger.error(f"Database entry not found! {ex}") with database.atomic(): Server_Stats.update(waiting_start=value).where(Server_Stats.server_id == server_id).execute() @@ -232,4 +245,4 @@ class helper_servers: return waiting_start.waiting_start -servers_helper = helper_servers() \ No newline at end of file +servers_helper = helper_servers() diff --git a/app/classes/models/users.py b/app/classes/models/users.py index c58d4a4a..e154e885 100644 --- a/app/classes/models/users.py +++ b/app/classes/models/users.py @@ -1,8 +1,7 @@ -import os import sys import logging import datetime -from typing import Optional, List, Union +from typing import Optional, Union from app.classes.shared.helpers import helper from app.classes.shared.console import console @@ -14,14 +13,12 @@ peewee_logger = logging.getLogger('peewee') peewee_logger.setLevel(logging.INFO) try: - from peewee import * + from peewee import SqliteDatabase, Model, ForeignKeyField, CharField, AutoField, DateTimeField, BooleanField, CompositeKey, DoesNotExist, JOIN from playhouse.shortcuts import model_to_dict - from enum import Enum - import yaml except ModuleNotFoundError as e: - logger.critical("Import Error: Unable to load {} module".format(e.name), exc_info=True) - console.critical("Import Error: Unable to load {} module".format(e.name)) + logger.critical(f"Import Error: Unable to load {e.name} module", exc_info=True) + console.critical(f"Import Error: Unable to load {e.name} module") sys.exit(1) database = SqliteDatabase(helper.db_path, pragmas={ @@ -58,7 +55,7 @@ class ApiKeys(Model): token_id = AutoField() name = CharField(default='', unique=True, index=True) created = DateTimeField(default=datetime.datetime.now) - user = ForeignKeyField(Users, backref='api_token', index=True) + user_id = ForeignKeyField(Users, backref='api_token', index=True) server_permissions = CharField(default='00000000') crafty_permissions = CharField(default='000') superuser = BooleanField(default=False) @@ -138,10 +135,12 @@ class helper_users: #logger.debug("user: ({}) {}".format(user_id, {})) return {} + @staticmethod def check_system_user(user_id): try: - Users.get(Users.user_id == user_id).user_id == user_id - return True + result = Users.get(Users.user_id == user_id).user_id == user_id + if result: + return True except: return False @@ -177,6 +176,7 @@ class helper_users: @staticmethod def get_super_user_list(): final_users = [] + # pylint: disable=singleton-comparison super_users = Users.select().where(Users.superuser == True) for suser in super_users: if suser.user_id not in final_users: @@ -233,7 +233,7 @@ class helper_users: @staticmethod def add_user_roles(user: Union[dict, Users]): - if type(user) == dict: + if isinstance(user, dict): user_id = user['user_id'] else: user_id = user.user_id @@ -246,7 +246,7 @@ class helper_users: for r in roles_query: roles.add(r.role_id.role_id) - if type(user) == dict: + if isinstance(user, dict): user['roles'] = roles else: user.roles = roles @@ -283,7 +283,12 @@ class helper_users: return ApiKeys.get(ApiKeys.token_id == key_id) @staticmethod - def add_user_api_key(name: str, user_id: str, superuser: bool = False, server_permissions_mask: Optional[str] = None, crafty_permissions_mask: Optional[str] = None): + def add_user_api_key( + name: str, + user_id: str, + superuser: bool = False, + server_permissions_mask: Optional[str] = None, + crafty_permissions_mask: Optional[str] = None): return ApiKeys.insert({ ApiKeys.name: name, ApiKeys.user_id: user_id, @@ -302,4 +307,4 @@ class helper_users: -users_helper = helper_users() \ No newline at end of file +users_helper = helper_users() diff --git a/app/classes/shared/cmd.py b/app/classes/shared/command.py similarity index 78% rename from app/classes/shared/cmd.py rename to app/classes/shared/command.py index d04eda30..001d8421 100644 --- a/app/classes/shared/cmd.py +++ b/app/classes/shared/command.py @@ -1,28 +1,16 @@ -import os import sys import cmd import time import threading import logging -from app.classes.shared.tasks import TasksManager - -logger = logging.getLogger(__name__) - from app.classes.shared.console import console from app.classes.shared.helpers import helper + from app.classes.web.websocket_helper import websocket_helper -try: - import requests - -except ModuleNotFoundError as e: - logger.critical("Import Error: Unable to load {} module".format(e.name), exc_info=True) - console.critical("Import Error: Unable to load {} module".format(e.name)) - sys.exit(1) - - -class MainPrompt(cmd.Cmd, object): +logger = logging.getLogger(__name__) +class MainPrompt(cmd.Cmd): def __init__(self, tasks_manager, migration_manager): super().__init__() @@ -30,16 +18,17 @@ class MainPrompt(cmd.Cmd, object): self.migration_manager = migration_manager # overrides the default Prompt - prompt = "Crafty Controller v{} > ".format(helper.get_version_string()) + prompt = f"Crafty Controller v{helper.get_version_string()} > " @staticmethod def emptyline(): pass + #pylint: disable=unused-argument def do_exit(self, line): self.tasks_manager._main_graceful_exit() self.universal_exit() - + def do_migrations(self, line): if line == 'up': self.migration_manager.up() @@ -52,9 +41,9 @@ class MainPrompt(cmd.Cmd, object): elif line == 'diff': console.info(self.migration_manager.diff) elif line == 'info': - console.info('Done: {}'.format(self.migration_manager.done)) - console.info('FS: {}'.format(self.migration_manager.todo)) - console.info('Todo: {}'.format(self.migration_manager.diff)) + console.info(f'Done: {self.migration_manager.done}') + console.info(f'FS: {self.migration_manager.todo}') + console.info(f'Todo: {self.migration_manager.diff}') elif line.startswith('add '): migration_name = line[len('add '):] self.migration_manager.create(migration_name, False) diff --git a/app/classes/shared/console.py b/app/classes/shared/console.py index 18733adc..43b8cd92 100644 --- a/app/classes/shared/console.py +++ b/app/classes/shared/console.py @@ -9,13 +9,12 @@ try: from termcolor import colored except ModuleNotFoundError as e: - logger.critical("Import Error: Unable to load {} module".format(e.name), exc_info=True) - print("Import Error: Unable to load {} module".format(e.name)) + logger.critical(f"Import Error: Unable to load {e.name} module", exc_info=True) + print(f"Import Error: Unable to load {e.name} module") from app.classes.shared.installer import installer installer.do_install() sys.exit(1) - class Console: def __init__(self): @@ -49,28 +48,27 @@ class Console: def debug(self, message): dt = datetime.datetime.now().strftime("%Y-%m-%d %I:%M:%S %p") - self.magenta("[+] Crafty: {} - DEBUG:\t{}".format(dt, message)) + self.magenta(f"[+] Crafty: {dt} - DEBUG:\t{message}") def info(self, message): dt = datetime.datetime.now().strftime("%Y-%m-%d %I:%M:%S %p") - self.white("[+] Crafty: {} - INFO:\t{}".format(dt, message)) + self.white(f"[+] Crafty: {dt} - INFO:\t{message}") def warning(self, message): dt = datetime.datetime.now().strftime("%Y-%m-%d %I:%M:%S %p") - self.cyan("[+] Crafty: {} - WARNING:\t{}".format(dt, message)) + self.cyan(f"[+] Crafty: {dt} - WARNING:\t{message}") def error(self, message): dt = datetime.datetime.now().strftime("%Y-%m-%d %I:%M:%S %p") - self.yellow("[+] Crafty: {} - ERROR:\t{}".format(dt, message)) + self.yellow(f"[+] Crafty: {dt} - ERROR:\t{message}") def critical(self, message): dt = datetime.datetime.now().strftime("%Y-%m-%d %I:%M:%S %p") - self.red("[+] Crafty: {} - CRITICAL:\t{}".format(dt, message)) + self.red(f"[+] Crafty: {dt} - CRITICAL:\t{message}") def help(self, message): dt = datetime.datetime.now().strftime("%Y-%m-%d %I:%M:%S %p") - self.green("[+] Crafty: {} - HELP:\t{}".format(dt, message)) + self.green(f"[+] Crafty: {dt} - HELP:\t{message}") console = Console() - diff --git a/app/classes/shared/exceptions.py b/app/classes/shared/exceptions.py index a5de82fa..05a46fdb 100644 --- a/app/classes/shared/exceptions.py +++ b/app/classes/shared/exceptions.py @@ -1,8 +1,8 @@ class CraftyException(Exception): - pass + pass class DatabaseException(CraftyException): - pass + pass class SchemaError(DatabaseException): - pass \ No newline at end of file + pass diff --git a/app/classes/shared/helpers.py b/app/classes/shared/helpers.py index 939c5842..a4566df5 100644 --- a/app/classes/shared/helpers.py +++ b/app/classes/shared/helpers.py @@ -14,15 +14,13 @@ import html import zipfile import pathlib import shutil -from requests import get -from contextlib import suppress import ctypes -import telnetlib -from app.classes.web.websocket_helper import websocket_helper - from datetime import datetime from socket import gethostname +from contextlib import suppress +from requests import get +from app.classes.web.websocket_helper import websocket_helper from app.classes.shared.console import console logger = logging.getLogger(__name__) @@ -32,9 +30,9 @@ try: from OpenSSL import crypto from argon2 import PasswordHasher -except ModuleNotFoundError as e: - logger.critical("Import Error: Unable to load {} module".format(e.name), exc_info=True) - console.critical("Import Error: Unable to load {} module".format(e.name)) +except ModuleNotFoundError as err: + logger.critical(f"Import Error: Unable to load {err.name} module", exc_info=True) + console.critical(f"Import Error: Unable to load {err.name} module") sys.exit(1) class Helpers: @@ -68,8 +66,8 @@ class Helpers: def check_file_perms(self, path): try: - fp = open(path, "r").close() - logger.info("{} is readable".format(path)) + open(path, "r", encoding='utf-8').close() + logger.info(f"{path} is readable") return True except PermissionError: return False @@ -82,7 +80,7 @@ class Helpers: return True else: return False - logger.error("{} does not exist".format(file)) + logger.error(f"{file} does not exist") return True def get_servers_root_dir(self): @@ -93,7 +91,7 @@ class Helpers: try: requests.get('https://google.com', timeout=1) return True - except Exception as err: + except Exception: return False @staticmethod @@ -139,7 +137,7 @@ class Helpers: esc = False # whether an escape character was encountered stch = None # if we're dealing with a quote, save the quote type here. Nested quotes to be dealt with by the command for c in cmd_in: # for character in string - if np == True: # if set, begin a new argument and increment the command index. Continue the loop. + if np: # if set, begin a new argument and increment the command index. Continue the loop. if c == ' ': continue else: @@ -154,11 +152,13 @@ class Helpers: else: if c == '\\': # if the current character is an escape character, set the esc flag and continue to next loop esc = True - elif c == ' ' and stch is None: # if we encounter a space and are not dealing with a quote, set the new argument flag and continue to next loop + elif c == ' ' and stch is None: # if we encounter a space and are not dealing with a quote, + # set the new argument flag and continue to next loop np = True elif c == stch: # if we encounter the character that matches our start quote, end the quote and continue to next loop stch = None - elif stch is None and (c in Helpers.allowed_quotes): # if we're not in the middle of a quote and we get a quotable character, start a quote and proceed to the next loop + elif stch is None and (c in Helpers.allowed_quotes): # if we're not in the middle of a quote and we get a quotable character, + # start a quote and proceed to the next loop stch = c else: # else, just store the character in the current arg cmd_out[ci] += c @@ -167,20 +167,20 @@ class Helpers: def get_setting(self, key, default_return=False): try: - with open(self.settings_file, "r") as f: + with open(self.settings_file, "r", encoding='utf-8') as f: data = json.load(f) if key in data.keys(): return data.get(key) else: - logger.error("Config File Error: setting {} does not exist".format(key)) - console.error("Config File Error: setting {} does not exist".format(key)) + logger.error(f"Config File Error: setting {key} does not exist") + console.error(f"Config File Error: setting {key} does not exist") return default_return except Exception as e: - logger.critical("Config File Error: Unable to read {} due to {}".format(self.settings_file, e)) - console.critical("Config File Error: Unable to read {} due to {}".format(self.settings_file, e)) + logger.critical(f"Config File Error: Unable to read {self.settings_file} due to {e}") + console.critical(f"Config File Error: Unable to read {self.settings_file} due to {e}") return default_return @@ -199,11 +199,11 @@ class Helpers: def get_version(self): version_data = {} try: - with open(os.path.join(self.config_dir, 'version.json'), 'r') as f: + with open(os.path.join(self.config_dir, 'version.json'), 'r', encoding='utf-8') as f: version_data = json.load(f) except Exception as e: - console.critical("Unable to get version data!") + console.critical(f"Unable to get version data! \n{e}") return version_data @@ -217,7 +217,7 @@ class Helpers: try: data = json.loads(r.content) except Exception as e: - logger.error("Failed to load json content with error: {} ".format(e)) + logger.error(f"Failed to load json content with error: {e}") return data @@ -225,11 +225,13 @@ class Helpers: def get_version_string(self): version_data = self.get_version() + major = version_data.get('major', '?') + minor = version_data.get('minor', '?') + sub = version_data.get('sub', '?') + meta = version_data.get('meta', '?') + # set some defaults if we don't get version_data from our helper - version = "{}.{}.{}-{}".format(version_data.get('major', '?'), - version_data.get('minor', '?'), - version_data.get('sub', '?'), - version_data.get('meta', '?')) + version = f"{major}.{minor}.{sub}-{meta}" return str(version) def encode_pass(self, password): @@ -240,7 +242,6 @@ class Helpers: self.passhasher.verify(currenthash, password) return True except: - pass return False def log_colors(self, line): @@ -264,6 +265,7 @@ class Helpers: # highlight users keywords for keyword in user_keywords: + # pylint: disable=consider-using-f-string search_replace = (r'({})'.format(keyword), r'\1') replacements.append(search_replace) @@ -274,7 +276,7 @@ class Helpers: def validate_traversal(self, base_path, filename): - logger.debug("Validating traversal (\"{x}\", \"{y}\")".format(x=base_path, y=filename)) + logger.debug(f"Validating traversal (\"{base_path}\", \"{filename}\")") base = pathlib.Path(base_path).resolve() file = pathlib.Path(filename) fileabs = base.joinpath(file).resolve() @@ -287,8 +289,8 @@ class Helpers: def tail_file(self, file_name, number_lines=20): if not self.check_file_exists(file_name): - logger.warning("Unable to find file to tail: {}".format(file_name)) - return ["Unable to find file to tail: {}".format(file_name)] + logger.warning(f"Unable to find file to tail: {file_name}") + return [f"Unable to find file to tail: {file_name}"] # length of lines is X char here avg_line_length = 255 @@ -297,7 +299,7 @@ class Helpers: line_buffer = number_lines * avg_line_length # open our file - with open(file_name, 'r') as f: + with open(file_name, 'r', encoding='utf-8') as f: # seek f.seek(0, 2) @@ -313,8 +315,7 @@ class Helpers: lines = f.readlines() except Exception as e: - logger.warning('Unable to read a line in the file:{} - due to error: {}'.format(file_name, e)) - pass + logger.warning(f'Unable to read a line in the file:{file_name} - due to error: {e}') # now we are done getting the lines, let's return it return lines @@ -323,14 +324,14 @@ class Helpers: def check_writeable(path: str): filename = os.path.join(path, "tempfile.txt") try: - fp = open(filename, "w").close() + open(filename, "w", encoding='utf-8').close() os.remove(filename) - logger.info("{} is writable".format(filename)) + logger.info(f"{filename} is writable") return True except Exception as e: - logger.critical("Unable to write to {} - Error: {}".format(path, e)) + logger.critical(f"Unable to write to {path} - Error: {e}") return False def checkRoot(self): @@ -360,24 +361,17 @@ class Helpers: try: with zipfile.ZipFile(zip_path, 'r') as zip_ref: zip_ref.extractall(tempDir) - for i in range(len(zip_ref.filelist)): + for i in enumerate(zip_ref.filelist): if len(zip_ref.filelist) > 1 or not zip_ref.filelist[i].filename.endswith('/'): - test = zip_ref.filelist[i].filename break - path_list = test.split('/') - root_path = path_list[0] - ''' - if len(path_list) > 1: - for i in range(len(path_list) - 2): - root_path = os.path.join(root_path, path_list[i + 1]) -''' + full_root_path = tempDir for item in os.listdir(full_root_path): try: shutil.move(os.path.join(full_root_path, item), os.path.join(new_dir, item)) except Exception as ex: - logger.error('ERROR IN ZIP IMPORT: {}'.format(ex)) + logger.error(f'ERROR IN ZIP IMPORT: {ex}') except Exception as ex: print(ex) else: @@ -394,28 +388,28 @@ class Helpers: # if not writeable, let's bomb out if not writeable: - logger.critical("Unable to write to {} directory!".format(self.root_dir)) + logger.critical(f"Unable to write to {self.root_dir} directory!") sys.exit(1) - # ensure the log directory is there + # ensure the log directory is there try: with suppress(FileExistsError): os.makedirs(os.path.join(self.root_dir, 'logs')) except Exception as e: - console.error("Failed to make logs directory with error: {} ".format(e)) + console.error(f"Failed to make logs directory with error: {e} ") # ensure the log file is there try: - open(log_file, 'a').close() + open(log_file, 'a', encoding='utf-8').close() except Exception as e: - console.critical("Unable to open log file!") + console.critical(f"Unable to open log file! {e}") sys.exit(1) # del any old session.lock file as this is a new session try: os.remove(session_log_file) except Exception as e: - logger.error("Deleting Session.lock failed with error: {} ".format(e)) + logger.error(f"Deleting Session.lock failed with error: {e}") @staticmethod def get_time_as_string(): @@ -424,10 +418,10 @@ class Helpers: @staticmethod def check_file_exists(path: str): - logger.debug('Looking for path: {}'.format(path)) + logger.debug(f'Looking for path: {path}') if os.path.exists(path) and os.path.isfile(path): - logger.debug('Found path: {}'.format(path)) + logger.debug(f'Found path: {path}') return True else: return False @@ -436,18 +430,20 @@ class Helpers: def human_readable_file_size(num: int, suffix='B'): for unit in ['', 'K', 'M', 'G', 'T', 'P', 'E', 'Z']: if abs(num) < 1024.0: + # pylint: disable=consider-using-f-string return "%3.1f%s%s" % (num, unit, suffix) num /= 1024.0 + # pylint: disable=consider-using-f-string return "%.1f%s%s" % (num, 'Y', suffix) @staticmethod def check_path_exists(path: str): if not path: return False - logger.debug('Looking for path: {}'.format(path)) + logger.debug(f'Looking for path: {path}') if os.path.exists(path): - logger.debug('Found path: {}'.format(path)) + logger.debug(f'Found path: {path}') return True else: return False @@ -459,17 +455,17 @@ class Helpers: if os.path.exists(path) and os.path.isfile(path): try: - with open(path, 'r') as f: + with open(path, 'r', encoding='utf-8') as f: for line in (f.readlines() [-lines:]): contents = contents + line return contents except Exception as e: - logger.error("Unable to read file: {}. \n Error: ".format(path, e)) + logger.error(f"Unable to read file: {path}. \n Error: {e}") return False else: - logger.error("Unable to read file: {}. File not found, or isn't a file.".format(path)) + logger.error(f"Unable to read file: {path}. File not found, or isn't a file.") return False def create_session_file(self, ignore=False): @@ -484,11 +480,10 @@ class Helpers: data = json.loads(file_data) pid = data.get('pid') started = data.get('started') - console.critical("Another Crafty Controller agent seems to be running...\npid: {} \nstarted on: {}".format(pid, started)) + console.critical(f"Another Crafty Controller agent seems to be running...\npid: {pid} \nstarted on: {started}") except Exception as e: - logger.error("Failed to locate existing session.lock with error: {} ".format(e)) - console.error("Failed to locate existing session.lock with error: {} ".format(e)) - + logger.error(f"Failed to locate existing session.lock with error: {e} ") + console.error(f"Failed to locate existing session.lock with error: {e} ") sys.exit(1) @@ -499,7 +494,7 @@ class Helpers: 'pid': pid, 'started': now.strftime("%d-%m-%Y, %H:%M:%S") } - with open(self.session_file, 'w') as f: + with open(self.session_file, 'w', encoding='utf-8') as f: json.dump(session_data, f, indent=True) # because this is a recursive function, we will return bytes, and set human readable later @@ -526,14 +521,14 @@ class Helpers: return sizes @staticmethod - def base64_encode_string(string: str): - s_bytes = str(string).encode('utf-8') + def base64_encode_string(fun_str: str): + s_bytes = str(fun_str).encode('utf-8') b64_bytes = base64.encodebytes(s_bytes) return b64_bytes.decode('utf-8') @staticmethod - def base64_decode_string(string: str): - s_bytes = str(string).encode('utf-8') + def base64_decode_string(fun_str: str): + s_bytes = str(fun_str).encode('utf-8') b64_bytes = base64.decodebytes(s_bytes) return b64_bytes.decode("utf-8") @@ -553,7 +548,7 @@ class Helpers: try: os.makedirs(path) - logger.debug("Created Directory : {}".format(path)) + logger.debug(f"Created Directory : {path}") # directory already exists - non-blocking error except FileExistsError: @@ -570,8 +565,8 @@ class Helpers: cert_file = os.path.join(cert_dir, 'commander.cert.pem') key_file = os.path.join(cert_dir, 'commander.key.pem') - logger.info("SSL Cert File is set to: {}".format(cert_file)) - logger.info("SSL Key File is set to: {}".format(key_file)) + logger.info(f"SSL Cert File is set to: {cert_file}") + logger.info(f"SSL Key File is set to: {key_file}") # don't create new files if we already have them. if self.check_file_exists(cert_file) and self.check_file_exists(key_file): @@ -602,11 +597,11 @@ class Helpers: cert.set_pubkey(k) cert.sign(k, 'sha256') - f = open(cert_file, "w") + f = open(cert_file, "w", encoding='utf-8') f.write(crypto.dump_certificate(crypto.FILETYPE_PEM, cert).decode()) f.close() - f = open(key_file, "w") + f = open(key_file, "w", encoding='utf-8') f.write(crypto.dump_privatekey(crypto.FILETYPE_PEM, k).decode()) f.close() @@ -645,7 +640,7 @@ class Helpers: data = {} if self.check_file_exists(default_file): - with open(default_file, 'r') as f: + with open(default_file, 'r', encoding='utf-8') as f: data = json.load(f) del_json = helper.get_setting('delete_default_json') @@ -662,25 +657,26 @@ class Helpers: for raw_filename in file_list: filename = html.escape(raw_filename) rel = os.path.join(folder, raw_filename) + dpath = os.path.join(folder, filename) if os.path.isdir(rel): output += \ - """
  • - \n
    - + f"""
  • + \n
    + - {} + {filename}
  • \n"""\ - .format(os.path.join(folder, filename), os.path.join(folder, filename), os.path.join(folder, filename), filename, os.path.join(folder, filename), os.path.join(folder, filename), filename, filename) + else: if filename != "crafty_managed.txt": - output += """
  • {}
  • """.format(os.path.join(folder, filename), filename, filename) + data-path="{dpath}" + data-name="{filename}" + onclick="clickOnFile(event)">{filename}""" return output @staticmethod @@ -688,29 +684,30 @@ class Helpers: file_list = os.listdir(folder) file_list = sorted(file_list, key=str.casefold) output += \ - """