diff --git a/Errors.py b/Errors.py deleted file mode 100644 index 56a4f266..00000000 --- a/Errors.py +++ /dev/null @@ -1,115 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -################### -# This package implements a web server to run scripts or executables -# from the command line and display the result in a web interface. -# Copyright (C) 2021, 2022, 2023 Maurice Lambert - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. - -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. - -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -################### - -""" -This tool runs CLI scripts and displays output in a Web Interface. - -This file contains WebScripts exception classes. -""" - -__version__ = "0.0.2" -__author__ = "Maurice Lambert" -__author_email__ = "mauricelambert434@gmail.com" -__maintainer__ = "Maurice Lambert" -__maintainer_email__ = "mauricelambert434@gmail.com" -__description__ = """ -This tool runs CLI scripts and displays output in a Web Interface. - -This file contains WebScripts exception classes. -""" -__license__ = "GPL-3.0 License" -__url__ = "https://github.com/mauricelambert/WebScripts" - -copyright = """ -WebScripts Copyright (C) 2021, 2022, 2023 Maurice Lambert -This program comes with ABSOLUTELY NO WARRANTY. -This is free software, and you are welcome to redistribute it -under certain conditions. -""" -license = __license__ -__copyright__ = copyright - -__all__ = [ - "WebScriptsError", - "WebScriptsConfigurationError", - "WebScriptsArgumentError", - "ScriptConfigurationError", - "MissingAttributesError", - "WebScriptsConfigurationTypeError", - "WebScriptsSecurityError", -] - - -class WebScriptsError(Exception): - """ - To raise WebScripts errors - """ - - pass - - -class WebScriptsConfigurationError(WebScriptsError): - """ - To raise Configuration Error. - """ - - pass - - -class WebScriptsConfigurationTypeError(WebScriptsConfigurationError): - """ - To raise Configuration Error. - """ - - pass - - -class WebScriptsArgumentError(WebScriptsError): - """ - To raise Argument Error. - """ - - pass - - -class ScriptConfigurationError(WebScriptsConfigurationError): - """ - To raise Script Configuration Error. - """ - - pass - - -class MissingAttributesError(WebScriptsError): - """ - To raise Missing Attributes Error. - """ - - pass - - -class WebScriptsSecurityError(WebScriptsError): - """ - To raise Security Error in WebScripts services. - """ - - pass diff --git a/Pages.py b/Pages.py deleted file mode 100644 index bc5592fd..00000000 --- a/Pages.py +++ /dev/null @@ -1,1098 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -################### -# This tool runs CLI scripts and displays output in a Web Interface. -# Copyright (C) 2021, 2022, 2023 Maurice Lambert - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. - -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. - -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -################### - -""" -This tool runs CLI scripts and displays output in a Web Interface. - -This file implement Pages (Api and Web system), script execution and right -system. -""" - -__version__ = "2.0.4" -__author__ = "Maurice Lambert" -__author_email__ = "mauricelambert434@gmail.com" -__maintainer__ = "Maurice Lambert" -__maintainer_email__ = "mauricelambert434@gmail.com" -__description__ = """ -This tool runs CLI scripts and displays output in a Web Interface. - -This file implement Pages (Api and Web system), script execution and right -system. -""" -license = "GPL-3.0 License" -__url__ = "https://github.com/mauricelambert/WebScripts" - -copyright = """ -WebScripts Copyright (C) 2021, 2022, 2023 Maurice Lambert -This program comes with ABSOLUTELY NO WARRANTY. -This is free software, and you are welcome to redistribute it -under certain conditions. -""" -__license__ = license -__copyright__ = copyright - -__all__ = ["Pages"] - -from typing import Tuple, List, Dict, TypeVar, Iterable, Union -from subprocess import Popen, PIPE, TimeoutExpired # nosec -from os import _Environ, path, environ as base_environ -from base64 import urlsafe_b64encode -from contextlib import suppress -from secrets import token_bytes -from json import dumps, loads -from string import printable -from fnmatch import fnmatch -from threading import Timer -from html import escape -from time import time - -Server = TypeVar("Server") - -try: - from .commons import ( - Argument, - User, - ScriptConfig, - CallableFile, - TokenCSRF, - Blacklist, - Session, - JsonValue, - ServerConfiguration, - DefaultNamespace, - get_ini_dict, - lib_directory, - current_directory, - log_trace, - get_ip, - Logs, - get_file_content, - get_arguments_count, - # doRollover, - # rotator, - # namer, - # Handler, - get_real_path, - get_encodings, - WebScriptsSecurityError, - WebScriptsArgumentError, - WebScriptsConfigurationError, - WebScriptsConfigurationTypeError, - logger_debug, - logger_info, - logger_auth, - logger_access, - logger_response, - logger_command, - logger_warning, - logger_error, - logger_critical, - check_file_permission, - ) -except ImportError: - from commons import ( - Argument, - User, - ScriptConfig, - CallableFile, - TokenCSRF, - Blacklist, - Session, - JsonValue, - ServerConfiguration, - DefaultNamespace, - get_ini_dict, - lib_directory, - current_directory, - log_trace, - get_ip, - Logs, - get_file_content, - get_arguments_count, - # doRollover, - # rotator, - # namer, - # Handler, - get_real_path, - get_encodings, - WebScriptsSecurityError, - WebScriptsArgumentError, - WebScriptsConfigurationError, - WebScriptsConfigurationTypeError, - logger_debug, - logger_info, - logger_auth, - logger_access, - logger_response, - logger_command, - logger_warning, - logger_error, - logger_critical, - check_file_permission, - ) - - -@log_trace -def execute_scripts( - script_name: str, - user: User, - environ: _Environ, - arguments: List[str], - inputs: List[str], - is_auth: bool = False, -) -> Tuple[bytes, bytes, str, int, str]: - """ - This function execute script from script name and return output and - errors. - """ - - script = Pages.scripts.get(script_name) - - if script is None: - return None, b"404", None, -1, "Not Found" - - if not is_auth and not check_right(user, script): - return None, b"403", None, -1, "Forbidden" - - arguments.insert(0, script.path) - - launcher = script.launcher - if launcher is not None: - arguments.insert(0, launcher) - - script_env = get_environ(environ, user, script) - - process = Popen( - arguments, - stdin=PIPE, - stdout=PIPE, - stderr=PIPE, - shell=False, - env=script_env, - ) # nosec - - stdout, stderr, key, error, code = start_process( - script, process, user, inputs - ) - - execution_logs(script, user, process, stderr) - return stdout, stderr, key, code, error - - -@log_trace -def start_process( - script: ScriptConfig, process: Popen, user: User, inputs: List[str] -) -> Tuple[bytes, bytes, str, str, int]: - """ - This function starts a process. - """ - - error = "No errors" - - if getattr(script, "print_real_time", None): - key = urlsafe_b64encode(token_bytes(40)).decode() - process_ = Pages.processes[key] = Process(process, script, user, key) - process_.send_inputs(inputs) - logger_debug( - "Create key and process, sent inputs and get the first line." - ) - return b"", b"", key, None, None - - try: - stdout, stderr = process.communicate( - input="\n".join(inputs).encode("latin-1"), - timeout=script.timeout, - ) - except TimeoutExpired: - logger_error(f"TimeoutExpired on {script.name} for {user.name}") - process.kill() - - stdout, stderr = process.communicate() - error = "TimeoutError" - - return stdout, stderr, None, error, process.returncode - - -@log_trace -def anti_XSS(json_value: JsonValue) -> JsonValue: - """ - This function clean a JSON object. - """ - - if isinstance(json_value, str): - return escape(json_value) - elif isinstance(json_value, int): - return json_value - elif isinstance(json_value, list): - return [anti_XSS(value) for value in json_value] - elif isinstance(json_value, dict): - return { - attribut: anti_XSS(value) for attribut, value in json_value.items() - } - elif json_value is None: - return json_value - else: - raise NotImplementedError( - f"type({type(json_value)}) is not implemented, supported types " - "are: \n\t - str\n\t - int\n\t - list\n\t - dict\n\t - NoneType" - ) - - -@log_trace -def execution_logs( - script: ScriptConfig, user: User, process: Popen, stderr: bytes -) -> None: - """ - This function logs the script execution. - """ - - if script.no_password: - logger_command(f"Command: {process.args}") - - if process.returncode or stderr: - logger_error( - f"SCRIPT ERROR: script {script.name!r} user {user.name!r} code " - f"{process.returncode} STDERR {decode_output(stderr)}" - ) - else: - logger_debug( - f"SCRIPT {script.name!r} executed without error for " - f"user named {user.name!r}." - ) - - -@log_trace -def get_environ( - environ: _Environ, user: User, script: ScriptConfig -) -> Dict[str, str]: - """ - This function builds the environment variables for the new process. - """ - - for var_name, value in environ.items(): - if var_name not in base_environ: - environ - - script_env = { - key: ( - ( - str(value) - if key in base_environ - else "".join(x for x in str(value) if x in printable) - ) - if key != "wsgi.version" - else ".".join([str(version) for version in value]) - ) - for key, value in environ.items() - if key - not in ( - "wsgi.run_once", - "wsgi.input", - "wsgi.errors", - "wsgi.file_wrapper", - ) - } - - user_dict = user.get_dict() - user_dict.pop("csrf", None) - user_dict.pop("check_csrf", None) - script_env["USER"] = dumps(user_dict) - script_env["SCRIPT_CONFIG"] = dumps(script.get_JSON_API()) - - return script_env - - -@log_trace -def check_right(user: User, configuration: ScriptConfig) -> bool: - """ - This function check rights for script/user and return boolean. - """ - - if ( - user.groups - and configuration.access_groups - and any(group in user.groups for group in configuration.access_groups) - ): - return check_categories_scripts_access(user, configuration) - elif configuration.access_users and user.id in configuration.access_users: - return check_categories_scripts_access(user, configuration) - elif isinstance(configuration.minimum_access, int) and any( - group >= configuration.minimum_access for group in user.groups - ): - return check_categories_scripts_access(user, configuration) - elif all( - v is None - for v in ( - configuration.minimum_access, - configuration.access_users, - configuration.access_groups, - ) - ): - return check_categories_scripts_access(user, configuration) - else: - logger_error( - f"HTTP 403: Access denied for {user.name} on {configuration.name}" - ) - return False - - -@log_trace -def check_categories_scripts_access( - user: User, configuration: ScriptConfig -) -> bool: - """ - This function check access on script and categories. - """ - - if any( - [fnmatch(configuration.category, access) for access in user.categories] - ): - logger_info( - f"{user.name} get access to category {configuration.category}" - f" ({user.categories})" - ) - return True - elif any([fnmatch(configuration.name, access) for access in user.scripts]): - logger_info( - f"{user.name} get access to script {configuration.name}" - f" ({user.scripts})" - ) - return True - else: - logger_error( - f"HTTP 403: {user.name} doesn't match with category " - f"({configuration.category}) and script ({configuration.name})" - ) - return False - - -@log_trace -def decode_output(data: bytes) -> str: - """ - This function decodes outputs (try somes encoding). - """ - - output = None - encodings = get_encodings() - encoding = next(encodings) - - while encoding is not None: - with suppress(UnicodeDecodeError): - output = data.decode(encoding) - return output - - encoding = next(encodings) - - -class Process: - """ - This class implements a running processus. - """ - - def __init__( - self, process: Popen, script: ScriptConfig, user: User, key: str - ): - logger_debug("Process creation...") - self.process = process - self.script = script - self.user = user - self.key = key - self.start_time = time() - self.timeout = script.timeout - - self.error = "No errors" - - if script.timeout is not None: - logger_info("Timeout detected, make timer and start it.") - self.timer = Timer(script.timeout, self.get_line, args=(False,)) - self.stop_max_time = self.start_time + self.timeout - self.timer.start() - - def get_line(self, read: bool = True) -> Tuple[bytes, bytes, str]: - """ - This function gets a new line from the script execution. - """ - - self.process.stdout.flush() - if self.process.poll() is not None: - if self.key in Pages.processes: - del Pages.processes[self.key] - - self.timer.cancel() - - stdout = self.process.stdout - stderr = self.process.stderr - - out = stdout.read() - error = stderr.read() - - stdout.close() - stderr.close() - - return ( - out, - error, - self.error, - ) - - elif self.timeout is None or time() <= self.stop_max_time: - data = self.process.stdout.readline() - logger_debug( - f"Get new line on {self.script.name} for {self.user.name}" - ) - return data, b"", "" - else: - logger_error( - f"TimeoutExpired on {self.script.name} for {self.user.name}" - ) - self.process.kill() - - if read: - fdout = self.process.stdout - fderr = self.process.stderr - stdout = fdout.read() - stderr = fderr.read() - fdout.close() - fderr.close() - del Pages.processes[self.key] - else: - self.timer = Timer(300, self.get_line) - # delete the process 5 minutes after the timeout - self.timer.start() - stdout = b"" - stderr = b"" - - self.error = "TimeoutError" - logger_debug("Get the stdout and the stderr of the killed process") - - return stdout, stderr, self.error - - def send_inputs(self, inputs: List[str]) -> None: - """ - This function send inputs to the child process. - """ - - self.process.stdin.flush() - for input_ in inputs: - self.process.stdin.write(f"{input_}\n".encode("latin-1")) - self.process.stdin.flush() - - self.process.stdin.close() - logger_debug( - "Inputs are sent to the child process and the stdin is closed." - ) - - -class Script: - """ - This class groups script functions. - """ - - @log_trace - def get( - self, - environ: _Environ, - user: User, - server: Server, - filename: str, - command: List[str], - inputs: List[str], - csrf_token: str = None, - ) -> Tuple[str, Dict[str, str], Union[str, bytes]]: - """ - This method send a new line of script execution in API response. - """ - - process = Pages.processes.get(filename) - - if process is None: - logger_error( - f"HTTP 404 for {user.name} on " f"/api/script/get/{filename}" - ) - return "404", {}, b"" - - if process.user.id != user.id: - logger_error( - f"HTTP 403 for {user.name} on " - f"/api/script/get/{process.script.name}" - ) - return "403", {}, b"" - - stdout, stderr, error = process.get_line() - code = process.process.returncode - - response_object = { - "stdout": decode_output(stdout) if stdout else "", - "stderr": decode_output(stderr) if stderr else "", - "code": code, - "Content-Type": process.script.content_type, - "Stderr-Content-Type": (process.script.stderr_content_type), - "error": error, - } - - if code is None: - response_object["key"] = filename - - return ( - "200 OK", - {"Content-Type": "application/json; charset=utf-8"}, - dumps(response_object), - ) - - -class Api: - """ - This class groups API functions. - """ - - script: Script = Script() - - @log_trace - def __call__( - self, - environ: _Environ, - user: User, - server: Server, - filename: str, - command: List[str], - inputs: List[str], - csrf_token: str = None, - ) -> Tuple[str, Dict[str, str], str]: - """ - This function return a json string with script informations and - arguments. - """ - - server_configuration = server.configuration - - has_auth = ( - server_configuration.auth_script - and server_configuration.active_auth - ) - unauthenticated_not_accepted = ( - not server_configuration.accept_unknow_user and user.id == 1 - ) or ( - not server_configuration.accept_unauthenticated_user - and user.id == 0 - ) - - if has_auth: - auth_script = Pages.scripts[ - server_configuration.auth_script - ].get_JSON_API() - auth_script["name"] = "/auth/" - auth_script["category"] = "Authentication" - - scripts = {"/auth/": auth_script} - else: - scripts = {} - - if unauthenticated_not_accepted: - return ( - "200 OK", - {"Content-Type": "application/json; charset=utf-8"}, - dumps(scripts), - ) - - for name, script in Pages.scripts.items(): - if name == server_configuration.auth_script: - continue - - if check_right(user, script): - scripts[name] = script.get_JSON_API() - - return ( - "200 OK", - {"Content-Type": "application/json; charset=utf-8"}, - dumps(scripts), - ) - - @log_trace - def scripts( - self, - environ: _Environ, - user: User, - server: Server, - filename: str, - command: List[str], - inputs: List[str], - csrf_token: str = None, - ) -> Tuple[str, Dict[str, str], Union[str, bytes]]: - """ - This function execute scripts with command line, - get output, build the response error, headers and body - and return it as json. - """ - - server_configuration = server.configuration - - if filename == server_configuration.auth_script: - logger_error( - f"HTTP 404 for {user.name} on /api/scripts/{filename}" - " (auth script)" - ) - return "404", {}, b"" - - environ_getter = environ.get - if user.check_csrf and not TokenCSRF.check_csrf( - user, - csrf_token, - getattr(server_configuration, "csrf_max_time", 300), - environ_getter("HTTP_REFERER"), - server.get_baseurl(environ_getter, environ), - ): - logger_error( - f"HTTP 403 for {user.name} on /api/scripts/{filename}" - " (CSRF Token invalid)" - ) - return "403", {}, b"" - - stdout, stderr, key, code, error = execute_scripts( - filename, user, environ, command, inputs - ) - - if stdout is None: - error_HTTP = stderr.decode() - logger_error( - f"HTTP {error_HTTP} for {user.name} on " - f"/api/scripts/{filename}" - ) - return error_HTTP, {}, b"" - - script = Pages.scripts[filename] - - response_object = { - "stdout": decode_output(stdout) if stdout else "", - "stderr": decode_output(stderr) if stderr else "", - "code": code, - "Content-Type": script.content_type, - "Stderr-Content-Type": (script.stderr_content_type), - "error": error, - } - - if key: - response_object["key"] = key - - if user.check_csrf: - response_object["csrf"] = TokenCSRF.build_token(user) - - return ( - "200 OK", - {"Content-Type": "application/json; charset=utf-8"}, - dumps(response_object), - ) - - -class Web: - """ - This class groups Web Pages functions. - """ - - @log_trace - def __call__( - self, - environ: _Environ, - user: User, - server: Server, - filename: str, - command: List[str], - inputs: List[str], - csrf_token: str = None, - ) -> Tuple[str, Dict[str, str], str]: - """ - This function return the index page (error code, headers, content). - """ - - return ( - "200 OK", - ( - { - "Content-Security-Policy": ( - "default-src 'self'; navigate-to 'self'; worker-src 'none'" - "; style-src-elem 'self'; style-src-attr 'none'; style-src" - " 'self'; script-src-attr 'none'; object-src 'none'; " - "media-src 'none'; manifest-src 'none'; frame-ancestors " - "'none'; connect-src 'self'; font-src 'none'; img-src " - "'self'; base-uri 'none'; child-src 'none'; form-action " - "'none'; script-src 'self' 'require-trusted-types-for'" - ) - } - if server.security - else { - "Content-Security-Policy-Report-Only": ( - "default-src 'self'; navigate-to 'self'; worker-src " - "'none'; style-src-elem 'self'; style-src-attr 'none';" - " style-src 'self'; script-src-attr 'none'; object-src" - " 'none'; media-src 'none'; manifest-src 'none'; " - "frame-ancestors 'none'; connect-src 'self'; font-src" - " 'none'; img-src 'self'; base-uri 'none'; child-src" - " 'none'; form-action 'none'; script-src 'self' " - "'require-trusted-types-for'; report-uri /csp/debug/" - ) - } - ), - ( - CallableFile.template_index - if server.security - else CallableFile.template_index.replace( - "Content-Security-Policy", - "Content-Security-Policy-Report-Only", - ) - ) - % { - "footer": CallableFile.template_footer, - "header": CallableFile.template_header, - }, - ) - - @log_trace - def doc( - self, - environ: _Environ, - user: User, - server: Server, - filename: str, - command: List[str], - inputs: List[str], - csrf_token: str = None, - ) -> Tuple[str, Dict[str, str], Union[str, bytes, Iterable[bytes]]]: - """ - This function return Web Page with scripts documentation. - """ - - script = Pages.scripts.get(filename) - - if script is None: - logger_error(f"HTTP 404 for {user.name} on /web/doc/{filename}") - return "404", {}, b"" - - if not check_right(user, script): - logger_error( - f"HTTP 403: Access denied for {user.name} on {filename} " - "(/web/doc/ request)" - ) - return "403", {}, b"" - - command_template = script.command_generate_documentation - if command_template is not None: - command = command_template % script.get_dict() - logger_command(f"Command for documentation: {command!r}") - process = Popen( # nosec # nosemgrep - command, - env=get_environ(environ, user, script), - shell=True, # nosec # nosemgrep - ) # nosec # nosemgrep - process.communicate() - - docfile = get_real_path(script.documentation_file) - if docfile is not None and path.isfile(docfile): - return ( - "200 OK", - { - "Content-Type": f"{script.documentation_content_type};" - " charset=utf-8" - }, - get_file_content(docfile, as_iterator=True), - ) - else: - doc = ScriptConfig.get_docfile_from_configuration( - server.configuration, filename - ) - - if doc is not None: - return ( - "200 OK", - { - "Content-Type": f"{script.documentation_content_type};" - " charset=utf-8" - }, - get_file_content(doc, as_iterator=True), - ) - - logger_error(f"HTTP 404 for {user.name} on /web/doc/{filename}") - return "404", {}, b"" - - @log_trace - def scripts( - self, - environ: _Environ, - user: User, - server: Server, - filename: str, - command: List[str], - inputs: List[str], - csrf_token: str = None, - ) -> Tuple[str, Dict[str, str], Union[bytes, str, Iterable[bytes]]]: - """ - This function return Web (HTML) response - (error code, headers and page) to call script - and print script output - """ - - server_configuration = server.configuration - - if filename == server_configuration.auth_script: - logger_error( - f"HTTP 404 for {user.name} on /web/scripts/{filename}" - " (auth script)" - ) - return "404", {}, b"" - - script = Pages.scripts.get(filename) - - if script is None: - logger_error( - f"HTTP 404 for {user.name} on /web/scripts/{filename}" - ) - return "404", {}, b"" - - if not check_right(user, script): - logger_error( - f"HTTP 403: Access denied for {user.name} on {filename}" - " (/web/scripts/ request)" - ) - return "403", {}, b"" - - callable_file = CallableFile( - "script", filename, filename, security=server.security - ) - - if callable_file is not None: - return callable_file(user) - - logger_error(f"HTTP 404 for {user.name} on /web/scripts/{filename}") - return "404", {}, b"" - - @log_trace - def auth( - self, - environ: _Environ, - user: User, - server: Server, - filename: str, - command: List[str], - inputs: List[str], - csrf_token: str = None, - ) -> Tuple[str, Dict[str, str], Union[bytes, Iterable[bytes]]]: - """ - This function returns authentication page (URL: /web/auth/). - """ - - server_configuration = server.configuration - - if server_configuration.active_auth: - code, headers, content = CallableFile( - "script", server_configuration.auth_script, "/auth/" - )(user, environ["SUB_DIRECTORIES_NUMBER"]) - return code, headers, content - else: - logger_error( - f"HTTP 404 for {user.name} on /web/auth/ (active_auth" - " configuration is not True)" - ) - return "404", {}, b"" - - -class Pages: - """ - This class implement Web Pages for WebScripts server. - """ - - packages: DefaultNamespace - scripts: Dict[str, ScriptConfig] - js_paths: Dict[str, CallableFile] - statics_paths: Dict[str, CallableFile] - sessions: Dict[int, Session] = {} - ip_blacklist: Dict[str, Blacklist] = {} - user_blacklist: Dict[str, Blacklist] = {} - processes: Dict[str, Process] = {} - api = Api() - web = Web() - - def __call__( - self, - environ: _Environ, - user: User, - server: Server, - filename: str, - command: List[str], - inputs: List[str], - csrf_token: str = None, - ) -> Tuple[str, Dict[str, str], bytes]: - """ - A redirect page (Error code 301, javascript redirect and redirect - title) to /web/ or /api/. - """ - - subpath = "../" * environ["SUB_DIRECTORIES_NUMBER"] - - return ( - "301 Moved Permanently", - {"Location": subpath + "web/"}, - ( - f"" - f'

Index page is {subpath}web/

Please click here' - ).encode(), - ) - - @staticmethod - @log_trace - def webfile( - files: Dict[str, CallableFile], user: User, filename: str, base: str - ) -> Tuple[str, Dict[str, str], Union[bytes, Iterable[bytes]]]: - """ - This function builds response for Web files. - """ - - callable_file = files.get(filename, None) - - if callable_file is not None: - return callable_file(user) - - logger_error(f"HTTP 404 for {user.name!r} on /{base}/{filename}") - return "404", {}, b"" - - @log_trace - def js( - self, - environ: _Environ, - user: User, - server: Server, - filename: str, - command: List[str], - inputs: List[str], - csrf_token: str = None, - ) -> Tuple[str, Dict[str, str], Union[bytes, Iterable[bytes]]]: - """ - This function get Javascripts Scripts and send it. - """ - - return self.webfile(Pages.js_paths, user, filename, "js") - - @log_trace - def static( - self, - environ: _Environ, - user: User, - server: Server, - filename: str, - command: List[str], - inputs: List[str], - csrf_token: str = None, - ) -> Tuple[str, Dict[str, str], Union[bytes, Iterable[bytes]]]: - """ - This function get static file and send it. - """ - - return self.webfile(Pages.statics_paths, user, filename, "static") - - @log_trace - def auth( - self, - environ: _Environ, - user: User, - server: Server, - filename: str, - command: List[str], - inputs: List[str], - csrf_token: str = None, - ) -> Tuple[str, Dict[str, str], bytes]: - """ - This function return check auth and return headers, error and page. - """ - - ip = environ["REMOTE_IP"] - server_configuration = server.configuration - - if not server_configuration.active_auth: - logger_error( - f"HTTP 403: Access denied for {user.name} on /auth/ " - "(active_auth configuration is not True)" - ) - return "403", {}, b"" - - logger_info("Run authentication script.") - stdout, stderr, key, code, error = execute_scripts( - server_configuration.auth_script, - user, - environ, - command, - inputs, - is_auth=True, - ) - - if len(stderr) == 3: - return stderr.decode(), {}, b"" - - if code or stdout is None or stderr: - return "500", {}, b"" - - user = User.default_build(**anti_XSS(loads(stdout))) - - if user.id != 0: - Pages.ip_blacklist[ip] = Blacklist( - server_configuration, Pages.ip_blacklist.pop(ip, None) - ) - if "--username" in command: - user_index = command.index("--username") + 1 - username = command[user_index] - Pages.user_blacklist[username] = Blacklist( - server_configuration, - Pages.user_blacklist.pop(username, None), - ) - logger_auth( - f"{user.name!r} ({user.id}) successfully authenticated" - f" from {ip} on {environ['PATH_INFO']}" - ) - - cookie = Session.build_session(user, ip, Pages) - - return ( - "302 Found", - { - # "Location": "/web/", - "Set-Cookie": f"SessionID={cookie}; Path=/; SameSite=Strict;" - f""" Max-Age={getattr( - server_configuration, - 'session_max_time', - 3600)}; Secure; HttpOnly""", - }, - b"", - ) - - def reload( - self, - environ: _Environ, - user: User, - server: Server, - filename: str, - command: List[str], - inputs: List[str], - csrf_token: str = None, - ) -> Tuple[str, Dict[str, str], str]: - """ - This function is a simple URL to reload scripts - (useful for developpers to add/modify a script). - """ - - pass diff --git a/WebScripts.py b/WebScripts.py deleted file mode 100644 index c4824226..00000000 --- a/WebScripts.py +++ /dev/null @@ -1,2178 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -################### -# This tool runs CLI scripts and displays output in a Web Interface. -# Copyright (C) 2021, 2022, 2023 Maurice Lambert - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. - -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. - -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -################### - -""" -This tool runs CLI scripts and displays output in a Web Interface. - -This file is the "main" file of this package (implements the main function, -the Server class and the Configuration class). -""" - -__version__ = "1.0.7" -__author__ = "Maurice Lambert" -__author_email__ = "mauricelambert434@gmail.com" -__maintainer__ = "Maurice Lambert" -__maintainer_email__ = "mauricelambert434@gmail.com" -__description__ = """ -This tool runs CLI scripts and displays output in a Web Interface. - -This file is the "main" file of this package (implements the main function, -the Server class and the Configuration class). -""" -license = "GPL-3.0 License" -__url__ = "https://github.com/mauricelambert/WebScripts" - -copyright = """ -WebScripts Copyright (C) 2021, 2022, 2023 Maurice Lambert -This program comes with ABSOLUTELY NO WARRANTY. -This is free software, and you are welcome to redistribute it -under certain conditions. -""" -__license__ = license -__copyright__ = copyright - -__all__ = ["Configuration", "Server", "main"] - -from os.path import basename, abspath, join, dirname, normpath, exists, isdir -from types import SimpleNamespace, ModuleType, FunctionType, MethodType -from typing import TypeVar, Tuple, List, Dict, Union, Set, Iterable -from sys import exit, modules as sys_modules, argv -from os import _Environ, mkdir, environ -from collections.abc import Iterator, Callable -from argparse import Namespace, ArgumentParser -from traceback import print_exc, format_exc -from json.decoder import JSONDecodeError -from logging.config import fileConfig -from collections import defaultdict -from wsgiref import simple_server -from urllib.parse import quote -from threading import Thread -from base64 import b64decode -from platform import system -from json import loads -from glob import iglob -import logging -import sys - -if __package__: - from .hardening import main as hardening - from .Pages import ( - Pages, - Argument, - User, - ScriptConfig, - CallableFile, - TokenCSRF, - Blacklist, - Session, - JsonValue, - DefaultNamespace, - get_ini_dict, - lib_directory as server_path, - current_directory, - log_trace, - get_ip, - Logs, - get_environ, - get_file_content, - get_arguments_count, - get_real_path, - WebScriptsSecurityError, - WebScriptsArgumentError, - WebScriptsConfigurationError, - WebScriptsConfigurationTypeError, - logger_debug, - logger_info, - logger_access, - logger_response, - logger_command, - logger_warning, - logger_error, - logger_critical, - check_file_permission, - ) -else: - from hardening import main as hardening - from Pages import ( - Pages, - Argument, - User, - ScriptConfig, - CallableFile, - TokenCSRF, - Blacklist, - Session, - JsonValue, - DefaultNamespace, - get_ini_dict, - lib_directory as server_path, - current_directory, - log_trace, - get_ip, - Logs, - get_environ, - get_file_content, - get_arguments_count, - get_real_path, - WebScriptsSecurityError, - WebScriptsArgumentError, - WebScriptsConfigurationError, - WebScriptsConfigurationTypeError, - logger_debug, - logger_info, - logger_access, - logger_response, - logger_command, - logger_warning, - logger_error, - logger_critical, - check_file_permission, - ) - -NameSpace = TypeVar("NameSpace", SimpleNamespace, Namespace) -FunctionOrNone = TypeVar("FunctionOrNone", FunctionType, None) -Content = TypeVar( - "Content", List[Dict[str, JsonValue]], Dict[str, JsonValue], bytes -) - - -class Configuration(DefaultNamespace): - """ - This class build the configuration from dict(s) with - configuration files and arguments. - """ - - __defaults__ = { - "interface": "127.0.0.1", - "port": 8000, - "urls": {}, - "modules": [], - "js_path": [], - "cgi_path": [], - "log_level": 0, - "statics_path": [], - "scripts_path": [], - "json_scripts_config": [], - "ini_scripts_config": [], - "modules_path": [], - "exclude_auth_paths": ["/static/", "/js/"], - "exclude_auth_pages": ["/api/", "/auth/", "/web/auth/"], - "auth_script": None, - "active_auth": False, - "webproxy_number": None, - "smtp_starttls": None, - "smtp_ssl": None, - "documentations_path": [], - "accept_unknow_user": True, - "force_file_permissions": True, - "auth_failures_to_blacklist": None, - "blacklist_time": None, - "accept_unauthenticated_user": True, - } - __required__ = ("interface", "port") - __optional__ = ( - "debug", - "security", - "active_auth", - "auth_script", - "accept_unknow_user", - "accept_unauthenticated_user", - "exclude_auth_paths", - "exclude_auth_pages", - "modules", - "modules_path", - "js_path", - "statics_path", - "documentations_path", - "scripts_path", - "json_scripts_config", - "ini_scripts_config", - "log_level", - "log_filename", - "log_level", - "log_format", - "log_date_format", - "log_encoding", - "auth_failures_to_blacklist", - "blacklist_time", - "webproxy_number", - "base_url", - "smtp_server", - "smtp_starttls", - "smtp_password", - "smtp_port", - "smtp_ssl", - "admin_adresses", - "notification_address", - ) - __types__ = { - "port": int, - "debug": bool, - "security": bool, - "active_auth": bool, - "accept_unknow_user": bool, - "accept_unauthenticated_user": bool, - "admin_groups": List[int], - "modules": list, - "modules_path": list, - "js_path": list, - "statics_path": list, - "documentations_path": list, - "exclude_auth_paths": list, - "exclude_auth_pages": list, - "scripts_path": list, - "json_scripts_config": list, - "ini_scripts_config": list, - "auth_failures_to_blacklist": int, - "blacklist_time": int, - "smtp_starttls": bool, - "smtp_port": int, - "smtp_ssl": bool, - "admin_adresses": list, - "csrf_max_time": int, - "session_max_time": int, - } - - @log_trace - def add_conf(self, **kwargs): - """ - Add configurations from other configuration files found. - """ - - logger_info("Add configurations...") - - for key, value in kwargs.items(): - if value is not None: - dict_ = self.__dict__ - - logger_info(f"Add configuration {key}: {value}") - default_value = dict_.get(key) - - if isinstance(default_value, list): - if isinstance(value, str): - logger_debug( - "Add configuration list using INI/CFG syntax." - ) - value = value.split(",") - - if isinstance(value, list): - logger_debug( - "Add configuration list using JSON syntax." - ) - for value_ in value: - if value_ not in dict_[key]: - dict_[key].append(value_) - else: - logger_error( - "Error in configuration: " - "list should be a JSON list" - " or INI/CFG comma separated string." - f" (not: {value})" - ) - raise WebScriptsConfigurationTypeError( - f"Configuration list {key}: {value} can't be " - f"add to {default_value}" - ) - - elif isinstance(default_value, dict): - logger_debug(f"Add configuration dict... {key}={value}") - dict_[key].update(value) - else: - logger_debug(f'Add "basic" configuration {key}={value}.') - dict_[key] = value - - -class Server: - """ - This class implements the WebScripts server. - """ - - class CommonsClasses: - Argument = Argument - User = User - ScriptConfig = ScriptConfig - CallableFile = CallableFile - TokenCSRF = TokenCSRF - Blacklist = Blacklist - Session = Session - DefaultNamespace = DefaultNamespace - Pages = Pages - - @log_trace - def __init__(self, configuration: Configuration): - self.configuration = configuration - self.interface: str = configuration.interface - self.port: int = configuration.port - - logger_debug("Create default value for WebScripts server...") - self.unknow: Dict = {"id": 1, "name": "Unknow", "groups": [0, 1]} - self.not_authenticated: Dict = { - "id": 0, - "name": "Not Authenticated", - "groups": [0], - } - self.error: str = "200 OK" - self.pages_cache = defaultdict(lambda: (None, None)) - self.pages = Pages() - self.logs = Logs - self.routing_url = configuration.urls - - self.send_mail = send_mail - - if configuration.webproxy_number is not None: - configuration.webproxy_number += 1 - - version = self.version = ( - sys_modules[__package__].__version__ - if __package__ - else __version__ - ) - - logger_debug("Create default HTTP headers...") - headers = self.headers = { - "Server": f"WebScripts {version}", - "Content-Type": "text/html; charset=utf-8", - } - - logger_debug("Get security configuration...") - self.debug = getattr(configuration, "debug", False) - security = self.security = getattr(configuration, "security", True) - self.loglevel = getattr(configuration, "log_level", "DEBUG") - - self.path = server_path - self.research_filename = get_real_path - self.research_file_content = get_file_content - self.get_environ_strings = get_environ - self.environ = environ - - self.set_default_headers(headers, security, configuration) - self.add_module_or_package() - self.add_paths() - - packages = Pages.packages - packages = [getattr(packages, attr) for attr in dir(packages)] - environ["WEBSCRIPTS_MODULES"] = ":".join( - [ - package.__file__ - for package in packages - if isinstance(package, ModuleType) and package.__file__ - ] - ) - - @staticmethod - @log_trace - def set_default_headers( - headers: Dict[str, str], security: bool, configuration: Configuration - ) -> None: - """ - This function sets defaults headers. - """ - - logger_debug("Set defaults headers...") - - if security: - headers["Strict-Transport-Security"] = ( - "max-age=63072000; includeSubDomains; preload" - ) - headers["Content-Security-Policy"] = ( - "default-src 'self'; navigate-to 'self'; worker-src " - "'none'; style-src-elem 'self'; style-src-attr 'none';" - " style-src 'self'; script-src-attr 'none'; object-src" - " 'none'; media-src 'none'; manifest-src 'none'; " - "frame-ancestors 'none'; connect-src 'self'; font-src" - " 'none'; img-src 'self'; base-uri 'none'; child-src" - " 'none'; form-action 'none'; script-src 'self' " - "'require-trusted-types-for'" - ) - headers["X-Frame-Options"] = "deny" - headers["X-XSS-Protection"] = "1; mode=block" - headers["X-Content-Type-Options"] = "nosniff" - headers["Referrer-Policy"] = "origin-when-cross-origin" - headers["Cache-Control"] = ( - "no-cache, no-store, must-revalidate, private" - ) - headers["Pragma"] = "no-cache" - headers["Expires"] = "0" - headers["Clear-Site-Data"] = '"cache", "executionContexts"' - headers["Feature-Policy"] = ( - "payment 'none'; geolocation 'none'; " - "microphone 'none'; camera 'none'" - ) - headers["Permissions-Policy"] = ( - "microphone=(),camera=(),payment=(),geolocation=()" - ) - headers["Cross-Origin-Embedder-Policy"] = "require-corp" - headers["Cross-Origin-Opener-Policy"] = "same-origin" - headers["Cross-Origin-Resource-Policy"] = "same-origin" - headers["Sec-Fetch-Mode"] = "document" - headers["Sec-Fetch-Site"] = "same-site" - headers["Sec-Fetch-Mode"] = "navigate" - headers["Sec-Fetch-User"] = "?1" - headers["X-Server"] = "WebScripts" - else: - logger_warning( - "Load insecure HTTP headers for development environment..." - ) - if "csp" not in configuration.modules: - configuration.modules.append("csp") - if "Configurations" not in configuration.modules: - configuration.modules.append("Configurations") - if "modules" not in configuration.modules_path: - configuration.modules_path.append("modules") - if "/csp/debug/" not in configuration.exclude_auth_pages: - configuration.exclude_auth_pages.append("/csp/debug/") - - headers["Content-Security-Policy-Report-Only"] = ( - "default-src 'self'; navigate-to 'self'; worker-src " - "'none'; style-src-elem 'self'; style-src-attr 'none';" - " style-src 'self'; script-src-attr 'none'; object-src" - " 'none'; media-src 'none'; manifest-src 'none'; " - "frame-ancestors 'none'; connect-src 'self'; font-src" - " 'none'; img-src 'self'; base-uri 'none'; child-src" - " 'none'; form-action 'none'; script-src 'self' " - "'require-trusted-types-for'; report-uri /csp/debug/" - ) - - logger_info("Default HTTP headers are set.") - - @log_trace - def check_blacklist(self, user: User, ip: str) -> bool: - """ - This function checks that the IP and the - username are not in the blacklist. - """ - - logger_debug("Check blacklist...") - - pages = self.pages - configuration = self.configuration - - logger_debug("Check user blacklist...") - if user is not None: - name = user.name - user = pages.user_blacklist.get(user.id, None) - if user is not None and user.is_blacklist(configuration): - logger_critical( - f"User {name} is blacklisted " - f"({user.counter} attempt using IP {ip})" - ) - return False - - logger_debug("Check ip blacklist...") - ip_ = pages.ip_blacklist.get(ip, None) - if ip_ is not None and ip_.is_blacklist(configuration): - logger_critical(f"IP {ip} is blacklisted ({ip_.counter} attempt).") - return False - - logger_info("IP and user not blacklisted.") - return True - - @log_trace - def get_session(self, cookies: List[str], ip: str) -> User: - """ - This function return User from cookies. - """ - - for cookie in cookies: - logger_debug("Analyze a new cookie...") - - if cookie.startswith("SessionID="): - logger_debug("Session cookie is detected...") - user = Session.check_session( - cookie, - self.pages, - ip, - None, - getattr(self.configuration, "session_max_time", 3600), - ) - - if user is None: - logger_warning("Session cookie is not valid.") - continue - - if ip != user.ip: - logger_warning("Session IP is not valid.") - user = User.default_build( - ip=ip, check_csrf=True, **self.unknow - ) - else: - logger_info("Valid session detected.") - user.check_csrf = True - - return user - - @staticmethod - @log_trace - def use_basic_auth( - credentials: str, pages: Pages, *args - ) -> Tuple[str, Dict[str, str], str]: - """ - This function decodes basic auth and - authenticates user with it. - """ - - logger_debug("Basic Auth detected, decode credentials...") - credentials = b64decode(credentials.split(" ", maxsplit=1)[1]).decode() - - if ":" in credentials: - username, password = credentials.split(":", maxsplit=1) - - logger_info("Use authentication script...") - return pages.auth( - *args, - ["--username", username, "--password", password], - [], - ) - - logger_error( - "Basic auth detected with invalid " - "credentials (':' not in credentials)." - ) - - @log_trace - def check_auth(self, environ: _Environ) -> Tuple[User, bool]: - """ - This function check if user is authenticated and blacklisted. - """ - - environ_get = environ.get - logger_debug("Check auth...") - credentials = environ_get("HTTP_AUTHORIZATION") - api_key = environ_get("HTTP_API_KEY") - cookies = environ_get("HTTP_COOKIE") - token = environ_get("HTTP_API_TOKEN") - ip = environ_get("REMOTE_IP") - - pages = self.pages - configuration = self.configuration - not_authenticated = self.not_authenticated - check_session = Session.check_session - default_build = User.default_build - - user = None - headers = None - - if cookies is not None: - logger_debug("Cookie detected, try to get session...") - user = self.get_session(cookies.split("; "), ip) - - elif token is not None: - logger_debug("API token detected, try to get session...") - user = check_session( - token.split(";")[0], - pages, - ip, - None, - getattr(configuration, "session_max_time", 3600), - ) - - if ( - user is None - and credentials is not None - and credentials.startswith("Basic ") - ): - auth = self.use_basic_auth( - credentials, - pages, - environ, - default_build(ip=ip, **not_authenticated), - self, - configuration.auth_script, - ) - code, headers, content = ( - auth if auth is not None else (None, None, None) - ) - - elif api_key is not None: - logger_info("API key detected. Use authentication script...") - code, headers, content = pages.auth( - environ, - default_build(ip=ip, **not_authenticated), - self, - configuration.auth_script, - ["--api-key", api_key], - [], - ) - - if headers is not None: - logger_debug("Get user using new cookie...") - cookie = headers.get("Set-Cookie", "").split("; ")[0] - user = check_session( - cookie, - pages, - ip, - None, - getattr(configuration, "session_max_time", 3600), - ) - - logger_debug("Blacklist check...") - not_blacklisted = self.check_blacklist(user, ip) - - if user is None: - logger_info("No user [valid authentication] detected.") - return ( - default_build(ip=ip, **not_authenticated), - not_blacklisted, - ) - else: - logger_info("Authenticated user detected.") - return user, not_blacklisted - - @log_trace - def add_module_or_package(self) -> None: - """ - This function add packages and modules to build custom page. - """ - - modules_path = [] - configuration = self.configuration - append = modules_path.append - logger_info("Load modules and packages...") - - for module_path in configuration.modules_path[::-1]: - path2 = join(server_path, module_path) - - append(module_path) - append(path2) - logger_debug( - f'Add "{module_path}" and "{path2}" in python path...' - ) - - sys.path = modules_path + sys.path - - packages = Pages.packages = DefaultNamespace() - for package in configuration.modules: - logger_warning(f"Add package/module named: {package}") - - package = __import__(package) - path_ = package._webscripts_filepath = normpath(package.__file__) - - if check_file_permission(configuration, path_, recursive=True): - setattr(packages, package.__name__, package) - - logger_info("Remove new paths...") - for path_ in modules_path: - logger_debug(f"Remove {path_}") - sys.path.remove(path_) - - @log_trace - def add_paths(self) -> None: - """ - This function add js, static and scripts paths. - """ - - configuration = self.configuration - statics_paths = Pages.statics_paths = {} - js_paths = Pages.js_paths = {} - - logger_debug("Add scripts in Web pages...") - scripts = Pages.scripts = ( - ScriptConfig.build_scripts_from_configuration( - configuration, - ) - ) - logger_info("Scripts are in Web pages.") - - for dirname_ in (server_path, current_directory): - logger_debug(f"Trying to find JS and static path in {dirname_}...") - - for globs, type_, type_name, dict_ in ( - (configuration.js_path, "js", "javascript", js_paths), - ( - configuration.statics_path, - "static", - "static", - statics_paths, - ), - ): - for glob in globs: - glob = join(dirname_, normpath(glob)) - logger_debug( - f"Trying to find file matching with {glob}..." - ) - for file in iglob(glob): - filename = basename(file) - file_path = abspath(file) - - Logs.info(f"Find a {type_name} file: {file_path}") - - dict_[filename] = CallableFile( - type_, file_path, filename - ) - - if ( - configuration.active_auth - and configuration.auth_script not in scripts.keys() - ): - logger_error("Auth script not found in configurations.") - raise WebScriptsConfigurationError( - "Auth script not found in configurations." - ) - - @log_trace - def get_function_page( - self, path: str, filename: str - ) -> Tuple[FunctionOrNone, str, bool]: - """ - This function find function from URL path. - If the function is a WebScripts built-in function, - return the function, filename and True. Else return the - function, filename and False. - """ - - path = tuple(path.split("/")[1:-1]) - cache = self.pages_cache - - logger_debug("Trying to get function page from cache...") - function, is_not_package = cache[path] - - if function: - return function, filename, is_not_package - - get_attributes = self.get_attributes - pages = self.pages - - logger_debug( - "Trying to found function page from default WebScripts function..." - ) - function, is_not_package = get_attributes(pages, path) - - if function is None: - logger_debug("Trying to found function page from packages...") - function, is_not_package = get_attributes( - pages.packages, path, False - ) - - cache[path] = (function, is_not_package) - return function, filename, is_not_package - - @log_trace - def get_URLs(self) -> List[str]: - """ - This function return a list of urls (scripts, documentation...) - and the start of the URL of custom packages. - """ - - urls = ["/api/", "/web/", *self.routing_url.keys()] - append = urls.append - pages = self.pages - - if getattr(self.configuration, "active_auth", None): - append("/auth/") - append("/web/auth/") - - for script_name in pages.scripts.keys(): - append(f"/web/scripts/{script_name}") - append(f"/api/scripts/{script_name}") - append(f"/web/doc/{script_name}") - - for js_name in pages.js_paths.keys(): - append(f"/js/{js_name}") - - for static_name in pages.statics_paths.keys(): - append(f"/static/{static_name}") - - for package in dir(pages.packages): - if isinstance(getattr(pages.packages, package), ModuleType): - append(f"/{package}/...") - - return urls - - @staticmethod - @log_trace - def get_attributes( - object_: object, - attributes: List[str], - is_not_package: bool = True, - ) -> Tuple[FunctionOrNone, bool]: - """ - This function get recursive attribute from object. - """ - - def check_argument_count(): - if isinstance(object_, FunctionType) or type(object_) is type: - if arg_count == 7: - logger_info(f"Function page found {object_}.") - return object_, is_not_package - else: - return ValueError - elif isinstance(object_, MethodType): - if arg_count == 8: - logger_info(f"Method page found {object_}.") - return object_, is_not_package - else: - return ValueError - - for attribute in attributes: - logger_debug(f"Trying to get {attribute} from {object_}") - object_ = getattr(object_, attribute, None) - - if object_ is None: - return None, is_not_package - - logger_debug("Get arguments length and check it...") - arg_count = get_arguments_count(object_) - - function = check_argument_count() - if function is None: - if isinstance(object_, Callable): - object_ = object_.__call__ - function = check_argument_count() - - if function is not None and function is not ValueError: - return function - - logger_warning( - "The function cannot be called with 7 " - "arguments or the method cannot be called " - "with 8 arguments." - ) - return None, is_not_package - - @staticmethod - @log_trace - def get_inputs( - arguments: List[Dict[str, JsonValue]] - ) -> Tuple[List[str], List[str]]: - """ - This function returns inputs and arguments from arguments. - """ - - inputs = [] - append = inputs.append - remove = arguments.remove - - logger_debug("Extract inputs from WebScripts arguments...") - for i, argument in enumerate(arguments): - if argument["input"]: - # To protect secrets, do not log arguments ! - append(argument) - - logger_debug("Remove inputs from arguments...") - for i, input_ in enumerate(inputs): - # To protect secrets, do not log arguments ! - remove(input_) - inputs[i] = str(input_["value"]) - - logger_debug("Extract value from WebScripts arguments...") - for i, argument in enumerate(arguments): - arguments[i] = argument["value"] - - return inputs, arguments - - @staticmethod - @log_trace - def get_content_length(environ: _Environ) -> int: - """ - This function returns the content length. - """ - - content_length = environ.get("CONTENT_LENGTH", "0") - - if content_length.isdigit(): - logger_debug(f"Content-Length is valid ({content_length}).") - return int(content_length) - else: - logger_warning(f"Content-Length is not valid ({content_length}).") - return 0 - - @staticmethod - @log_trace - def try_get_command( - body: Dict[str, JsonValue] - ) -> Union[None, Tuple[Content, str, bool]]: - """ - This function returns arguments, CSRF token and True if is WebScripts - request. If is not a WebScripts request because there's no "arguments" - section in request content, this function returns None. If an error - is raised in arguments parser, this function returns the JSON - content, None and False. - """ - - body_get = body.get - arguments_ = body_get("arguments") - get_command = Argument.get_command - - arguments = [] - - if arguments_ is not None: - logger_debug('"arguments" section detected in request content.') - try: - for name, argument in arguments_.items(): - arguments += get_command(name, argument) - except (WebScriptsArgumentError, TypeError, AttributeError): - logger_error("Arguments detected is not in WebScripts format.") - return body, None, False - - return arguments, body_get("csrf_token"), True - - @staticmethod - @log_trace - def get_baseurl(environ_getter: Callable, environ: _Environ) -> str: - """ - This function returns URL base. - """ - - scheme = environ["wsgi.url_scheme"] - port = environ["SERVER_PORT"] - - host = environ_getter("HTTP_HOST") or ( - environ["SERVER_NAME"] - if (scheme == "https" and port == "443") - or (scheme == "http" and port == "80") - else f"{environ['SERVER_NAME']}:{port}" - ) - return f"{scheme}://{host}" - - @staticmethod - @log_trace - def get_fullurl(environ: _Environ) -> str: - """ - This function returns the full URL (based on the PEP 3333). - - Link: https://peps.python.org/pep-3333/ - """ - - scheme = environ["wsgi.url_scheme"] - url = scheme + "://" - - host = environ.get("HTTP_HOST") - query = environ.get("QUERY_STRING") - - if host: - url += host - else: - url += environ["SERVER_NAME"] - - if scheme == "https": - if environ["SERVER_PORT"] != "443": - url += ":" + environ["SERVER_PORT"] - else: - if environ["SERVER_PORT"] != "80": - url += ":" + environ["SERVER_PORT"] - - url += quote(environ.get("SCRIPT_NAME", "")) - url += quote(environ.get("PATH_INFO", "")) - - if query: - url += "?" + query - - return url - - @staticmethod - @log_trace - def check_origin(environ_getter: Callable, environ: _Environ) -> bool: - """ - This function checks Origin of POST methods. - """ - - logger_debug("Check origin...") - origin = environ_getter("HTTP_ORIGIN") - url = Server.get_baseurl(environ_getter, environ) - - if origin is None: - logger_info("No origin detected, rejected request.") - return False - - if origin.lstrip("htps") != url.lstrip("htps"): - logger_warning(f"Bad Origin detected: {origin!r} != {url!r}") - return False - - logger_info("Correct Origin detected.") - return True - - @staticmethod - @log_trace - def get_json_content(body: bytes, content_type: str) -> JsonValue: - """ - This functions returns the loaded JSON content. - """ - - logger_debug("Get JSON content...") - if content_type.startswith("application/json"): - try: - return loads(body) - except (JSONDecodeError, UnicodeDecodeError): - logger_warning("Non-JSON content detected.") - logger_info( - "This request is not available for" - " the default functions of WebScripts." - ) - - return None - - @log_trace - def parse_body(self, environ: _Environ) -> Tuple[Content, str, bool]: - """ - This function returns arguments from body. - """ - - environ_get = environ.get - - if not self.check_origin(environ_get, environ): - logger_warning("Bad Origin detected (CSRF protection).") - return [], None, False - - logger_debug("Read wsgi.input ...") - content_length = self.get_content_length(environ) - body = environ["wsgi.input"].read(content_length) - content_type = environ["CONTENT_TYPE"] - logger_debug("wsgi.input is read.") - - if content_length: - json_content = self.get_json_content(body, content_type) - - if not json_content: - return body, None, False - else: - body = json_content - - return_values = self.try_get_command(body) - if return_values is not None: - return return_values - - logger_warning( - 'Section "arguments" is not defined in the JSON content.' - ) - logger_info( - "This request is not available for" - " the default functions of WebScripts" - ) - return body, None, False - - return [], None, True - - @log_trace - def app(self, environ_: _Environ, respond: MethodType) -> List[bytes]: - """ - This function get function page, - return content page, catch errors and - return HTTP errors. - """ - - environ = self.environ.copy() - environ.update(environ_) - - base_url = getattr(self.configuration, "base_url", "").rstrip("/") - - environ["URL_PATH"] = path_info = environ["PATH_INFO"] - environ["PATH_INFO"] = path_info = ( - path_info[len(base_url) :] - if path_info.startswith(base_url) - else path_info - ) - environ["SUB_DIRECTORIES_NUMBER"] = len(path_info.split("/")) - 2 - method = environ["REQUEST_METHOD"] - configuration = self.configuration - port = environ.setdefault("REMOTE_PORT", "0") - ip = environ["REMOTE_IP"] = get_ip( - environ, configuration.webproxy_number - ) - logger_access( - f"Request ({method}) from {ip!r}:{port} on {path_info!r}." - ) - - if ip is None: - logger_critical("IP Spoofing: Error 403.") - return self.page_403(environ, self.unknow, "", None, respond) - - path_info_startswith = path_info.startswith - path, filename = path_info.rsplit("/", 1) - path += "/" - is_head_method = method == "HEAD" - - new_url = self.routing_url.get(path) - if new_url is not None: - logger_info(f"Routing URL: {path_info!r} to {new_url!r}.") - path = new_url - - logger_debug("Trying to get function page...") - get_response, filename, is_not_package = self.get_function_page( - path, filename - ) - - logger_debug("Check authentication...") - user, not_blacklisted = self.check_auth(environ) - - if not not_blacklisted: - logger_critical( - f"Blacklist: Error 403 on {path_info!r} for " - f"{user.name!r} (ID: {user.id})." - ) - return self.page_403(environ, user, filename, None, respond) - - logger_info("User is not blacklisted.") - logger_debug("Trying to get and parse body...") - - if method == "POST": - arguments, csrf_token, is_webscripts_request = self.parse_body( - environ - ) - elif method == "GET" or is_head_method: - arguments, csrf_token, is_webscripts_request = [], None, True - else: - return self.page_400(environ, user, filename, method, respond) - - arguments, inputs = self.return_inputs( - arguments, is_webscripts_request - ) - - if is_not_package and not is_webscripts_request: - logger_error(f"HTTP 406: for {user.name!r} on {path_info!r}") - error = "406" - else: - logger_info("Request is not rejected as HTTP error 406.") - error: str = None - - if ( - ( - (not configuration.accept_unknow_user and user.id == 1) - or ( - not configuration.accept_unauthenticated_user - and user.id == 0 - ) - ) - and configuration.active_auth - ) and ( - path_info not in configuration.exclude_auth_pages - and not any( - path_info_startswith(x) - for x in configuration.exclude_auth_paths - ) - ): - logger_warning( - f"Unauthenticated try to get access to {path_info!r}" - ) - self.send_headers( - environ, - respond, - "302 Found", - { - "Location": environ["SUB_DIRECTORIES_NUMBER"] * "../" - + "web/auth/" - }, - ) - return [ - b"Authentication required:\n\t", - b" - For API you can use Basic Auth", - b"\n\t - For API you can use Api-Key", - b"\n\t - For Web Interface (with Web Browser) use /web/auth/", - ] - - if get_response is None: - logger_info("Page 404, cause: no function page.") - return self.page_404(environ, user, filename, path_info, respond) - - if error == "406": - logger_debug("Send response (code 406).") - return self.page_406(environ, user, filename, None, respond) - - logger_debug("Trying to execute function page...") - try: - error, headers, page = get_response( - environ, - user, - self, - filename, - arguments, - inputs, - csrf_token=csrf_token, - ) - except Exception as error: - print_exc() - error_text = format_exc() - error = f"{error}\n{error_text}" - Logs.error(error) - return self.page_500(environ, user, filename, error_text, respond) - - if error == "404" and not page: - logger_debug("Send response 404, cause: function page return 404.") - return self.page_404(environ, user, filename, path_info, respond) - elif error == "403" and not page: - logger_debug("Send response 403, cause: function page return 403.") - return self.page_403(environ, user, filename, None, respond) - elif error == "500" and not page: - logger_debug("Send response 500, cause: function page return 500.") - return self.page_500(environ, user, filename, page, respond) - elif not page: - logger_debug(f"Get custom response for code {error}") - ( - custom_error, - custom_headers, - custom_content, - ) = self.send_custom_error(environ, user, filename, "", error) - if custom_content is not None: - logger_info(f"Get a response for code {error}") - error = custom_error - headers = custom_headers - page = custom_content - - error, headers = self.set_default_values_for_response(error, headers) - - logger_debug("Send headers...") - self.send_headers(environ, respond, error, headers) - if is_head_method: - logger_debug("Is HEAD method, return an empty response body...") - return [] - - return self.return_page(page) - - @log_trace - def set_default_values_for_response( - self, error: str, headers: Dict[str, str] - ) -> Tuple[str, Dict[str, str]]: - """ - This function returns default error if not defined and - default headers updated with custom headers. - """ - - if not error: - logger_debug("Set error code as default error code (200).") - error = self.error - - logger_debug("Add default and custom headers to the response...") - default_headers = self.headers.copy() - default_headers.update(headers) - - return error, default_headers - - @staticmethod - @log_trace - def return_page(page: Union[bytes, str, Iterable[bytes]]) -> List[bytes]: - """ - This function returns response as a list of bytes. - """ - - if isinstance(page, bytes): - logger_debug("Send bytes response...") - return [page] - elif isinstance(page, str): - logger_debug("Send str response (encode using utf-8)...") - return [page.encode()] - elif isinstance(page, Iterable): - logger_debug("Send iterable response...") - return page - - @log_trace - def return_inputs( - self, - arguments: List[Dict[str, JsonValue]], - is_webscripts_request: bool, - ) -> Tuple[List[str], List[str]]: - """ - This function returns inputs (using Server.get_inputs). - """ - - if is_webscripts_request: - logger_debug("Trying to get inputs...") - inputs, arguments = self.get_inputs(arguments) - else: - logger_info( - "Is not a WebScripts request, inputs are " - "defined as empty list." - ) - inputs = [] - - return arguments, inputs - - @log_trace - def send_headers( - self, - environ: _Environ, - respond: MethodType, - error: str = None, - headers: Dict[str, str] = None, - ) -> None: - """ - This function send error code, message and headers. - """ - - if error is None: - logger_debug("Defined error as default error.") - error = self.error - if headers is None: - logger_debug("Defined headers as default headers.") - _headers = self.headers - else: - logger_debug("Update headers with custom headers...") - _headers = self.headers.copy() - _headers.update(headers) - - logger_response( - f"Response {environ['REMOTE_IP']!r}:{environ['REMOTE_PORT']} " - f"{environ['REQUEST_METHOD']} {environ['PATH_INFO']} {error!r}" - ) - respond(error, [(k, v) for k, v in _headers.items()]) - - @log_trace - def page_500( - self, - environ: _Environ, - user: User, - filename: str, - error: Union[str, bytes, Iterable[bytes]], - respond: MethodType, - ) -> List[bytes]: - """ - This function return error 500 web page. - """ - - error_code = "500 Internal Error" - logger_debug("Send 500 Internal Error...") - return self.send_error_page( - environ, - user, - filename, - error_code, - b"".join(self.return_page(error)), - respond, - ) - - @log_trace - def page_404( - self, - environ: _Environ, - user: User, - filename: str, - url: str, - respond: MethodType, - ): - """ - This function return error 404 web page. - """ - - error_code = "404 Not Found" - - logger_debug("Get URLs for 404 debug page...") - urls = "\n\t - ".join(self.get_URLs()) - error = ( - f"This URL: {url}, doesn't exist" - f" on this server.\nURLs:\n\t - {urls}" - ) - logger_error(f"HTTP 404 on {url}") - return self.send_error_page( - environ, user, filename, error_code, error.encode(), respond - ) - - @log_trace - def page_400( - self, - environ: _Environ, - user: User, - filename: str, - method: str, - respond: MethodType, - ): - """ - This function return error 400 web page. - """ - - error_code = "400 Bad Request" - error = ( - "Bad method, method should be GET, " - f"POST or HEAD not {method}".encode() - ) - logger_debug("Send 400 Bad Request...") - return self.send_error_page( - environ, user, filename, error_code, error, respond - ) - - @log_trace - def page_401( - self, - environ: _Environ, - user: User, - filename: str, - error_description: str, - respond: MethodType, - ): - """ - This function return error 401 web page. - """ - - error_code = "401 Unauthorized" - error = b"Unauthorized (You don't have permissions)" - logger_debug("Send 401 Unauthorized...") - return self.send_error_page( - environ, user, filename, error_code, error, respond - ) - - @log_trace - def page_403( - self, - environ: _Environ, - user: User, - filename: str, - error_description: str, - respond: MethodType, - ): - """ - This function return error 403 web page. - """ - - error_code = "403 Forbidden" - error = b"Forbidden (You don't have permissions)" - logger_debug("Send 403 Forbidden...") - return self.send_error_page( - environ, user, filename, error_code, error, respond - ) - - @log_trace - def page_406( - self, - environ: _Environ, - user: User, - filename: str, - error_description: str, - respond: MethodType, - ): - """ - This function return error 406 web page. - """ - - error_code = "406 Not Acceptable" - error = ( - b"Not Acceptable, your request is not a valid WebScripts request." - ) - logger_debug("Send 406 Not Acceptable...") - return self.send_error_page( - environ, user, filename, error_code, error, respond - ) - - @log_trace - def send_error_page( - self, - environ: _Environ, - user: User, - filename: str, - error: str, - data: bytes, - respond: MethodType, - ) -> List[bytes]: - """ - This function send HTTP errors. - """ - - code = error[:3] - headers = {"Content-Type": "text/plain; charset=utf-8"} - error_ = "" - - logger_debug("Trying to get custom error response...") - try: - custom_error, custom_headers, custom_data = self.send_custom_error( - environ, user, filename, error, code - ) - except Exception as exception: - print_exc() - error_ = ( - f"{exception.__class__.__name__}: {exception}\n{format_exc()}" - ) - logger_error(error_) - custom_data = None - - if self.debug: - logger_warning("Send debug error page...") - self.send_headers(environ, respond, error, headers) - return [ - b"---------------\n", - f"** ERROR {code} **\n".encode(), - b"---------------\n", - b"\n\n", - data, - error_.encode(), - ] - - if custom_data is not None: - logger_debug("Send custom error page...") - self.send_headers(environ, respond, custom_error, custom_headers) - return custom_data - - logger_debug("Send default error page...") - self.send_headers(environ, respond, error, headers) - return [ - b"---------------\n", - f"** ERROR {code} **\n".encode(), - b"---------------\n", - ] - - def send_custom_error( - self, - environ: _Environ, - user: User, - filename: str, - error: str, - code: str, - ) -> Tuple[ - Union[str, None], Union[Dict[str, str], None], Union[str, None] - ]: - """ - This function call custom errors pages. - """ - - logger_debug("Search custom error in packages...") - - cache = self.pages_cache - function_name = "page_" + code - function, _ = cache[function_name] - - if function is not None: - logger_debug("Get custom error page (function) from cache.") - return function(environ, user, self, filename, error) - - packages = self.pages.packages - for package in dir(packages): - package = getattr(packages, package) - - if isinstance(package, ModuleType): - logger_debug(f"Check in {package}...") - page = getattr(package, function_name, None) - - if page is not None: - logger_info( - f"Found the custom error page: {package}.page_{code}" - ) - cache[function_name] = page, False - return page( - environ, - user, - self, - filename, - error, - ) - - return None, {}, None - - -@log_trace -def parse_args(argv: List[str] = argv) -> Namespace: - """ - This function parse command line arguments. - """ - - parser = ArgumentParser( - description="This package implements a web server to run scripts or " - "executables from the command line and display the result " - "in a web interface.", - ) - parser.add_argument( - "-i", "--interface", help="Interface to launch WebScripts server." - ) - parser.add_argument( - "-p", "--port", help="Port to launch WebScripts server.", type=int - ) - - parser.add_argument( - "-c", - "--config-cfg", - help="Config filename (syntax config,ini).", - action="extend", - nargs="+", - default=[], - ) - parser.add_argument( - "-j", - "--config-json", - help="Config filename (syntax json).", - action="extend", - nargs="+", - default=[], - ) - - dev = parser.add_argument_group( - "DEV", - "Arguments for development and debugging [do NOT use these arguments " - "in production !]", - ) - dev.add_argument( - "-d", - "--debug", - help="Debug (to get errors details).", - action="store_true", - default=None, - ) - dev.add_argument( - "-s", - "--security", - help="Remove HTTP security headers [Disable security]," - " active the Content-Security-Policy-Report-Only header" - ' and the CSP debug module (URL: "/csp/debug/")', - action="store_false", - default=None, - ) - - auth = parser.add_argument_group("AUTH", "authentication configurations") - auth.add_argument( - "-a", - "--active-auth", - help="Disable authentication page [Disable auth (force to accept " - "unknow and unauthenticated user)].", - action="store_false", - default=None, - ) - auth.add_argument("--auth-script", help="Script for authentication.") - auth.add_argument( - "--accept-unauthenticated-user", - help="Accept unauthenticated user.", - action="store_true", - default=None, - ) - auth.add_argument( - "--accept-unknow-user", - help="Accept unknow user.", - action="store_true", - default=None, - ) - auth.add_argument( - "-b", - "--auth-failures-to-blacklist", - type=int, - help="Number of authentication failures to blacklist an IP or user.", - ) - auth.add_argument( - "-B", - "--blacklist-time", - type=int, - help="Time (in seconds) to blacklist an IP or user.", - ) - auth.add_argument( - "--e-auth-paths", - "--exclude-auth-paths", - action="extend", - nargs="+", - default=[], - help="Start of paths where the unauthenticated user gets access.", - ) - auth.add_argument( - "--e-auth-pages", - "--exclude-auth-pages", - action="extend", - nargs="+", - default=[], - help="Specific page where the unauthenticated user has access.", - ) - - parser.add_argument( - "-S", - "--scripts-path", - help="Add directory to search scripts", - action="extend", - nargs="+", - default=[], - ) - parser.add_argument( - "-C", - "--scripts-config", - help="Add file for scripts configuration (glob syntax)", - action="extend", - nargs="+", - default=[], - ) - parser.add_argument( - "-m", - "--modules", - help="Add modules to add urls.", - nargs="+", - action="extend", - default=[], - ) - parser.add_argument( - "-I", - "--modules-path", - help="Add directory to search modules/packages", - action="extend", - nargs="+", - default=[], - ) - parser.add_argument( - "-D", - "--documentations-path", - help="Add directory to search documentations", - action="extend", - nargs="+", - default=[], - ) - parser.add_argument( - "-J", - "--js-path", - help="Add directory to get Javascript files.", - action="extend", - nargs="+", - default=[], - ) - parser.add_argument( - "-T", - "--statics-path", - help="Add directory to get static files", - action="extend", - nargs="+", - default=[], - ) - - logs = parser.add_argument_group("LOGS", "logs configurations") - logs.add_argument( - "-l", - "--log-level", - help="Log level for ROOT logger.", - choices=["0", "DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"], - ) - logs.add_argument( - "-f", "--log-filename", help="Log filename for ROOT logger." - ) - logs.add_argument("--log-format", help="Format for ROOT logger.") - logs.add_argument("--log-date-format", help="Date format for ROOT logger.") - logs.add_argument("--log-encoding", help="Encoding for ROOT logger.") - - smtp = parser.add_argument_group( - "SMTP", "SMTP configurations to send email notifications" - ) - smtp.add_argument( - "--smtp-server", - "--s-server", - help="The SMTP server to use to send email notification.", - ) - smtp.add_argument( - "--smtp-starttls", - "--s-tls", - help="Use STARTTLS to secure the connection.", - action="store_true", - default=None, - ) - smtp.add_argument( - "--smtp-password", - "--s-password", - help="The SMTP password to login the notification account.", - ) - smtp.add_argument( - "--smtp-port", - "--s-port", - help="The SMTP port to use to send email notification.", - type=int, - ) - smtp.add_argument( - "--smtp-ssl", - "--s-ssl", - help="Use SSL to secure the connection", - action="store_true", - default=None, - ) - smtp.add_argument( - "--admin-adresses", - "--a-adr", - help="The admintrators email addresses to receive the email " - "notifications.", - nargs="+", - action="extend", - ) - smtp.add_argument( - "--notification-address", - "--n-adr", - help="The email address to send notifications.", - ) - return parser.parse_args(argv[1:]) - - -@log_trace -def get_server_config( - arguments: Namespace, secure: bool = False -) -> Iterator[dict]: - """ - This generator return configurations dict. - """ - - logger_debug("Get paths for server configuration...") - paths = [ - join(current_directory, "config", "server.ini"), - join(current_directory, "config", "server.json"), - ] - insert = paths.insert - - temp_config = Configuration() - temp_config.force_file_permissions = secure - - if system() == "Windows": - logger_debug("Add default server configuration for Windows...") - insert(0, join(server_path, "config", "nt", "server.json")) - insert(0, join(server_path, "config", "nt", "server.ini")) - else: - logger_debug("Add default server configuration for Unix...") - insert(0, join(server_path, "config", "server.json")) - insert(0, join(server_path, "config", "server.ini")) - - for filename in paths: - logger_debug(f"Check {filename}...") - - if exists(filename) and check_file_permission(temp_config, filename): - logger_warning(f"Configuration file detected: {filename}") - if filename.endswith(".json"): - yield loads(get_file_content(filename)) - elif filename.endswith(".ini"): - yield get_ini_dict(filename) - else: - logger_info(f"Configuration named {filename} doesn't exists.") - - for filename in arguments.config_cfg: - logger_debug("Check configuration file (cfg) added in arguments...") - - if exists(filename) and check_file_permission(temp_config, filename): - logger_warning( - f"Configuration file detected (type cfg): {filename}" - ) - yield get_ini_dict(filename) - else: - logger_error( - f"Configuration file {filename} doesn't exists " - "(defined in arguments)." - ) - - for filename in arguments.config_json: - logger_debug("Check configuration file (json) added in arguments...") - - if exists(filename) and check_file_permission(temp_config, filename): - logger_warning( - f"Configuration file detected (type json): {filename}" - ) - yield loads(get_file_content(filename)) - else: - logger_error( - f"Configuration file {filename} doesn't exists " - "(defined in arguments)." - ) - - args = arguments.__dict__ - del args["config_cfg"] - del args["config_json"] - - yield {k: v for k, v in args.items() if v is not None} - - -@log_trace -def logs_configuration(configuration: NameSpace) -> None: - """ - This function configure ROOT logger from - configuration files and command line arguments. - """ - - log_config = {} - log_level = getattr(configuration, "log_level", 0) - - if isinstance(log_level, str) and log_level.isdigit(): - configuration.log_level = int(log_level) - elif isinstance(log_level, str): - configuration.log_level = getattr(logging, log_level, 0) - elif not isinstance(log_level, int): - raise WebScriptsConfigurationError( - "log_level configuration must be an integer or a " - 'string in ["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]' - ) - - for attr, item in { - "log_format": "format", - "log_date_format": "datefmt", - "log_encoding": "encoding", - "log_level": "level", - "log_filename": "filename", - }.items(): - value = getattr(configuration, attr, None) - if value is not None: - log_config[item] = value - - if log_config: - log_config["force"] = True - Logs.config(**log_config) - - -@log_trace -def add_configuration( - configuration: Configuration, config: Dict[str, JsonValue] -) -> Configuration: - """ - This function add configuration in ServerConfiguration. - """ - - current_configuration = Configuration() - add_conf = current_configuration.add_conf - have_server_conf = "server" in config.keys() - - if have_server_conf: - logger_debug('"server" section detected in configuration.') - server = config.pop("server") - - logger_debug("Adding other configuration in Configuration object...") - add_conf(**config) - - if have_server_conf: - logger_debug("Add server section in Configuration object...") - add_conf(**server) - - logger_debug("Build type of configurations...") - - logs_configuration(current_configuration) - current_configuration.build_types() - - config_dict = current_configuration.get_dict() - logger_debug( - "Add configurations in ServerConfiguration: " f"{config_dict}" - ) - configuration.add_conf(**config_dict) - return configuration - - -def configure_logs_system(secure: bool = False) -> Tuple[Set[str], Set[str]]: - """ - This function try to create the logs directory - if not found and configure logs. - """ - - if not isdir("logs"): - logger_info("./logs directory not found.") - try: - mkdir("logs") - except PermissionError: - logger_error( - "Get a PermissionError to create " - "the non-existent ./logs directory." - ) - else: - logger_info("./logs directory is created.") - - log_file = get_real_path(join("config", "loggers.ini")) - - temp_config = Configuration() - temp_config.force_file_permissions = secure - - if not check_file_permission(temp_config, log_file): - raise WebScriptsSecurityError( - "Logs configuration file/directory permissions are" - " insecure. Remote code execution can be exploited." - ) - - fileConfig( - log_file, - disable_existing_loggers=False, - ) - - Logs.log_response.handlers[0].baseFilename - - logs_path = set() - log_files = set() - - logs_path_add = logs_path.add - log_files_add = log_files.add - - for logger_ in ( - "log_trace", - "log_response", - "log_access", - "log_command", - "log_debug", - "log_info", - "log_warning", - "log_error", - "log_critical", - "file", - ): - logger = getattr(Logs, logger_) - handlers = logger.handlers - - for handler in handlers: - filepath = getattr(handler, "baseFilename", None) - if filepath is not None: - logs_path_add(dirname(filepath)) - log_files_add( - (logger_.split("_", 1)[1] if "_" in logger_ else "all") - + "?" - + filepath - ) - - environ["WEBSCRIPTS_LOGS_PATH"] = "|".join(logs_path) - environ["WEBSCRIPTS_LOGS_FILES"] = "|".join(log_files) - return logs_path, log_files - - -def send_mail(*args, **kwargs) -> int: - """ - This function send a mail to adminitrators - using the error_pages modules. - - Return 0 if message is sent else 1. - """ - - logger_debug("Get module error_pages...") - error_pages = getattr(Pages.packages, "error_pages", None) - if error_pages: - logger_debug("Start a Thread to send email...") - Thread( - target=error_pages.Request.send_mail, - args=args, - kwargs=kwargs, - ).start() - return 0 - - logger_error("Module error_pages is not detected, can not send email.") - return 1 - - -def default_configuration( - argv: List[str] = argv, secure: bool = False -) -> Configuration: - """ - This function builds the default configuration. - """ - - log_paths, log_files = configure_logs_system(secure) - environ["WEBSCRIPTS_PATH"] = server_path - args = parse_args(argv) - - logger_debug("Load configurations...") - - configuration = Configuration() - configuration.logs_path = list(log_paths) - configuration.log_files = list(log_files) - for config in get_server_config(args, secure): - configuration = add_configuration(configuration, config) - - configuration = add_configuration(configuration, args.__dict__) - - logger_debug("Check and type configurations...") - configuration.set_defaults() - configuration.check_required() - configuration.get_unexpecteds() - configuration.build_types() - - urls_section = configuration.get("urls_section") - - if urls_section is not None: - urls = getattr(configuration, urls_section, None) - - if urls is None: - raise WebScriptsConfigurationError( - f"The 'urls_section' ({urls_section!r}) " "does not exists." - ) - - if not isinstance(urls, dict) or not all( - isinstance(k, str) and isinstance(v, str) for k, v in urls.items() - ): - raise WebScriptsConfigurationError( - f"Key {urls_section!r} (the url section) should be a section" - ' of strings (dict or JSON object {"string": "string"}).' - ) - - configuration.urls = urls - else: - configuration.urls = {} - - configuration.__types__["log_level"] = int - configuration.data_dir = datapath = get_real_path( - getattr(configuration, "data_dir", "data"), is_dir=True - ) - - environ["WEBSCRIPTS_DATA_PATH"] = datapath - environ["WEBSCRIPTS_DOCUMENTATION_PATH"] = ":".join( - configuration.documentations_path - ) - - logger_info("Configurations are loaded.") - - return configuration - - -def prepare_server(secure: bool = True) -> Server: - """ - This function prepares server to be launched securly. - """ - - configuration = default_configuration(argv, secure) - debug = getattr(configuration, "debug", None) - - if debug: - logger_debug("Debug mode detected: export configuration...") - configuration.export_as_json() - - logger_debug("Build server with configurations...") - server = Server(configuration) - - return server, debug - - -def main() -> int: - """ - Main function to build the - configurations and launch the server. - """ - - if "--test-running" in argv: - NO_START = True - argv.remove("--test-running") - else: - NO_START = False - - secure: bool = "--security" not in argv and "-s" not in argv - - server, debug = prepare_server(secure) - httpd = simple_server.make_server( - server.interface, server.port, server.app - ) - logger_info("Server is built.") - - logger_debug("Trying to send email notification...") - send_mail( - server.configuration, - f"Server is up on http://{server.interface}:{server.port}/.", - ) - - logger_info("Check hardening of the WebScripts server...") - if secure: - hardening(server) - - if debug: - # second export to get all configurations - logger_debug("Debug mode detected: export configuration...") - server.configuration.export_as_json() - - logger_warning( - f"Starting server on http://{server.interface}:{server.port}/ ..." - ) - print(copyright) - - if NO_START: - logger_warning( - "Detected as test only. Do not start" - " the server. Exit with code 0." - ) - return 0 - - try: - httpd.serve_forever() - except KeyboardInterrupt: - logger_critical("Server is down.") - httpd.server_close() - - logger_debug("Trying to send email notification...") - send_mail( - server.configuration, - f"Server is down on http://{server.interface}:{server.port}/.", - ) - logger_debug("Exit with code 0.") - return 0 - - -if __name__ == "__main__": - exit(main()) diff --git a/WebScripts/__init__.py b/WebScripts/__init__.py index 0962225e..5a5677cf 100644 --- a/WebScripts/__init__.py +++ b/WebScripts/__init__.py @@ -23,7 +23,7 @@ This tool runs CLI scripts and displays output in a Web Interface. """ -__version__ = "3.0.38" +__version__ = "3.0.39" __author__ = "Maurice Lambert" __author_email__ = "mauricelambert434@gmail.com" __maintainer__ = "Maurice Lambert" diff --git a/WebScripts/__main__.py b/WebScripts/__main__.py index de4c53e5..3aac887a 100644 --- a/WebScripts/__main__.py +++ b/WebScripts/__main__.py @@ -23,7 +23,7 @@ This tool runs CLI scripts and displays output in a Web Interface. """ -__version__ = "3.0.38" +__version__ = "3.0.39" __author__ = "Maurice Lambert" __author_email__ = "mauricelambert434@gmail.com" __maintainer__ = "Maurice Lambert" diff --git a/WebScripts/harden.py b/WebScripts/harden.py index 552617f7..89948ba9 100644 --- a/WebScripts/harden.py +++ b/WebScripts/harden.py @@ -25,7 +25,7 @@ This file hardens the WebScripts installation and configuration. """ -__version__ = "0.0.9" +__version__ = "0.0.10" __author__ = "Maurice Lambert" __author_email__ = "mauricelambert434@gmail.com" __maintainer__ = "Maurice Lambert" @@ -323,7 +323,7 @@ def harden_script(self, section: dict, filename: str) -> None: """ logger_info("Hardens script " + repr(filename)) - logger_debug("Add the launcher") + logger_debug(f"Add launcher {executable!r} for {filename!r}") section["launcher"] = executable specific_config_file = section.get("configuration_file") @@ -331,18 +331,28 @@ def harden_script(self, section: dict, filename: str) -> None: specific_config_file = basename(specific_config_file) script_name, _ = splitext(basename(filename)) - logger_info(f"Configure script named: {script_name}") + logger_info("Configure script named: " + repr(script_name)) for config_file in self.json_config_files: if config_file.endswith(specific_config_file): section["configuration_file"] = config_file self.get_configurations(config_file, filename) break + else: + logger_error( + "Configuration file not found for " + repr(filename) + ) for py_filename in self.py_scripts_files: - if py_filename.endswith(filename): - logger_debug("Add the script absolute path.") + py_basename = basename(py_filename) + if py_basename == filename: + logger_debug( + "Add the script absolute path" + f" {py_filename!r} for {filename!r}." + ) section["path"] = py_filename break + else: + logger_error("Script file not found for " + repr(filename)) def linux_hardening_file_permissions(self) -> None: """ diff --git a/WebScripts/static/html/index.html b/WebScripts/static/html/index.html index 233b61de..2110e910 100644 --- a/WebScripts/static/html/index.html +++ b/WebScripts/static/html/index.html @@ -6,7 +6,7 @@  
__init__ (version 3.0.39)
 
- 
__init__ (version 3.0.38)
index
__init__.py

This tool runs CLI scripts and displays output in a Web Interface.

diff --git a/__init__.py b/__init__.py deleted file mode 100644 index 5a5677cf..00000000 --- a/__init__.py +++ /dev/null @@ -1,53 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -################### -# This tool runs CLI scripts and displays output in a Web Interface. -# Copyright (C) 2021, 2022, 2023, 2024 Maurice Lambert - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. - -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. - -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -################### - -""" -This tool runs CLI scripts and displays output in a Web Interface. -""" - -__version__ = "3.0.39" -__author__ = "Maurice Lambert" -__author_email__ = "mauricelambert434@gmail.com" -__maintainer__ = "Maurice Lambert" -__maintainer_email__ = "mauricelambert434@gmail.com" -__description__ = ( - "This tool runs CLI scripts and displays output in a Web Interface." -) -__license__ = "GPL-3.0 License" -__url__ = "https://github.com/mauricelambert/WebScripts" - -copyright = """ -WebScripts Copyright (C) 2021, 2022, 2023, 2024 Maurice Lambert -This program comes with ABSOLUTELY NO WARRANTY. -This is free software, and you are welcome to redistribute it -under certain conditions. -""" -license = __license__ -__copyright__ = copyright - -print(copyright) - -__all__ = ["Configuration", "Server", "main"] - -if __package__: - from .WebScripts import Configuration, Server, main -else: - from WebScripts import Configuration, Server, main diff --git a/__main__.py b/__main__.py deleted file mode 100644 index 3aac887a..00000000 --- a/__main__.py +++ /dev/null @@ -1,53 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -################### -# This tool runs CLI scripts and displays output in a Web Interface. -# Copyright (C) 2021, 2022, 2023, 2024 Maurice Lambert - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. - -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. - -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -################### - -""" -This tool runs CLI scripts and displays output in a Web Interface. -""" - -__version__ = "3.0.39" -__author__ = "Maurice Lambert" -__author_email__ = "mauricelambert434@gmail.com" -__maintainer__ = "Maurice Lambert" -__maintainer_email__ = "mauricelambert434@gmail.com" -__description__ = ( - "This tool runs CLI scripts and displays output in a Web Interface." -) -__license__ = "GPL-3.0 License" -__url__ = "https://github.com/mauricelambert/WebScripts" - -copyright = """ -WebScripts Copyright (C) 2021, 2022, 2023, 2024 Maurice Lambert -This program comes with ABSOLUTELY NO WARRANTY. -This is free software, and you are welcome to redistribute it -under certain conditions. -""" -license = __license__ -__copyright__ = copyright - -__all__ = ["main"] - -if __package__: - from .__init__ import main -else: - from __init__ import main - -main() diff --git a/cgi-bin/hello.py b/cgi-bin/hello.py deleted file mode 100644 index 7b6c2308..00000000 --- a/cgi-bin/hello.py +++ /dev/null @@ -1,116 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -################### -# This tool run scripts and display the result in a Web Interface. -# Copyright (C) 2021, 2022 Maurice Lambert - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. - -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. - -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -################### - -""" -This tool run scripts and display the result in a Web Interface. - -This file implements a CGI script for example (parse arguments and body). -""" - -__version__ = "1.0.0" -__author__ = "Maurice Lambert" -__author_email__ = "mauricelambert434@gmail.com" -__maintainer__ = "Maurice Lambert" -__maintainer_email__ = "mauricelambert434@gmail.com" -__description__ = """ -This tool run scripts and display the result in a Web Interface. - -This file implements a CGI script for example (parse arguments and body). -""" -__license__ = "GPL-3.0 License" -__url__ = "https://github.com/mauricelambert/WebScripts" - -copyright = """ -WebScripts Copyright (C) 2021, 2022 Maurice Lambert -This program comes with ABSOLUTELY NO WARRANTY. -This is free software, and you are welcome to redistribute it -under certain conditions. -""" -license = __license__ -__copyright__ = copyright - -__all__ = ["parse_args"] - -from cgi import FieldStorage, parse, MiniFieldStorage -from urllib.parse import unquote, parse_qs - -# from cgitb import enable -from os import environ -from sys import argv - -# enable() # debug mode - - -def parse_args(*args, **kwargs) -> None: - - """ - This function parses arguments/body with - differents functions/tools. - """ - - print("\t\t - Simple parsing:") - if len(argv) == 2: - arguments = unquote(argv[1]) - print(f"\t\t\t - Arguments: {argv[0]!r} {argv[1]!r}") - else: - arguments = parse_qs( - environ["QUERY_STRING"], *args, **kwargs - ) or parse(*args, **kwargs) - for key, values in arguments.items(): - print( - "\t\t\t - ", - repr(key), - "=", - *[(repr(v) + ", ") for v in values], - ) - - print("\t\t - Complex parsing:") - arguments = FieldStorage(*args, **kwargs) - for argument_name in arguments.keys(): - value = arguments[argument_name] - if isinstance(value, MiniFieldStorage): - print( - "\t\t\t - ", - repr(argument_name), - "=", - repr(value.value) + ",", - value, - ) - elif isinstance(value, list): - print( - "\t\t\t - ", - repr(argument_name), - "=", - [(repr(v.value) + ",") for v in value], - *value, - ) - - -print("Content-Type: text/plain") -print() -print("Hello world !") - -print("\t 1. Don't keep blank values: ") -parse_args() -print("\t 2. Keep blank values: ") -parse_args(keep_blank_values=True) - -print("- WebScripts -") diff --git a/cgi-bin/test.py b/cgi-bin/test.py deleted file mode 100644 index 12a5cda1..00000000 --- a/cgi-bin/test.py +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -print("Content-Type: text/plain") -print() -print("Hello world !") \ No newline at end of file diff --git a/commons.py b/commons.py deleted file mode 100644 index e9c19466..00000000 --- a/commons.py +++ /dev/null @@ -1,1199 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -################### -# This tool runs CLI scripts and displays output in a Web Interface. -# Copyright (C) 2021, 2022, 2023 Maurice Lambert - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. - -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. - -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -################### - -""" -This tool runs CLI scripts and displays output in a Web Interface. - -This file implements commons functions and class for WebScripts package. -""" - -__version__ = "0.1.7" -__author__ = "Maurice Lambert" -__author_email__ = "mauricelambert434@gmail.com" -__maintainer__ = "Maurice Lambert" -__maintainer_email__ = "mauricelambert434@gmail.com" -__description__ = """ -This tool runs CLI scripts and displays output in a Web Interface. - -This file implements commons functions and class for WebScripts package. -""" -license = "GPL-3.0 License" -__url__ = "https://github.com/mauricelambert/WebScripts" - -copyright = """ -WebScripts Copyright (C) 2021, 2022, 2023 Maurice Lambert -This program comes with ABSOLUTELY NO WARRANTY. -This is free software, and you are welcome to redistribute it -under certain conditions. -""" -__license__ = license -__copyright__ = copyright - -__all__ = [ - "User", - "Session", - "Argument", - "JsonValue", - "TokenCSRF", - "Blacklist", - "ScriptConfig", - "CallableFile", - "ServerConfiguration", -] - - -from os.path import join, normcase, isfile, dirname, splitext, basename, split -from typing import TypeVar, Tuple, List, Dict -from secrets import token_bytes, token_hex -from configparser import ConfigParser -from collections.abc import Callable -from subprocess import Popen, PIPE # nosec -from types import SimpleNamespace -from base64 import b64encode -from re import fullmatch -from glob import iglob -from time import time -from json import load - -if __package__: - from .utils import ( - DefaultNamespace, - get_ini_dict, - server_path as lib_directory, - working_directory as current_directory, - log_trace, - get_ip, - Logs, - get_file_content, - get_arguments_count, - # doRollover, - # rotator, - # namer, - # Handler, - get_real_path, - get_encodings, - WebScriptsConfigurationError, - WebScriptsArgumentError, - WebScriptsConfigurationTypeError, - WebScriptsSecurityError, - logger_debug, - logger_info, - logger_auth, - logger_access, - logger_response, - logger_command, - logger_warning, - logger_error, - logger_critical, - IS_WINDOWS, - check_file_permission, - ) -else: - from utils import ( - DefaultNamespace, - get_ini_dict, - server_path as lib_directory, - working_directory as current_directory, - log_trace, - get_ip, - Logs, - get_file_content, - get_arguments_count, - # doRollover, - # rotator, - # namer, - # Handler, - get_real_path, - get_encodings, - WebScriptsConfigurationError, - WebScriptsArgumentError, - WebScriptsConfigurationTypeError, - WebScriptsSecurityError, - logger_debug, - logger_info, - logger_auth, - logger_access, - logger_response, - logger_command, - logger_warning, - logger_error, - logger_critical, - IS_WINDOWS, - check_file_permission, - ) - -JsonValue = TypeVar("JsonValue", str, int, bool, None, List[str], List[int]) -ServerConfiguration = TypeVar("ServerConfiguration") -ScriptConfig = TypeVar("ScriptConfig") -Blacklist = TypeVar("Blacklist") -Pages = TypeVar("Pages") - -Configuration = TypeVar("Configuration", ServerConfiguration, SimpleNamespace) - - -class Argument(DefaultNamespace): - """ - This class build argument for script. - """ - - __required__ = ["name"] - __optional__ = [ - "list", - "input", - "example", - "html_type", - "is_advanced", - "description", - "default_value", - "predefined_values", - "javascript_attributs", - ] - __defaults__ = { - "javascript_attributs": {}, - "default_value": None, - "is_advanced": False, - "html_type": "text", - "description": None, - "example": None, - "input": None, - } - __types__ = { - "predefined_values": list, - "is_advanced": bool, - "input": bool, - "list": bool, - } - - @staticmethod - @log_trace - def get_command( - name: str, argument: Dict[str, JsonValue] - ) -> List[Dict[str, JsonValue]]: - """ - This function return list for command line execution. - """ - - if name.startswith("-"): - list_ = [{"value": name, "input": False}] - argument["input"] = False - else: - list_ = [] - - value = argument["value"] - - if value is None: - return [] - elif isinstance(value, str): - if value == "": - return [] - - list_.append(argument) - - elif isinstance(value, bool): - if value is False: - return [] - else: - return [{"value": name, "input": False}] - - elif isinstance(value, (int, float)): - list_.append({"value": str(value), "input": argument["input"]}) - - elif isinstance(value, list): - while "" in value: - value.remove("") - - while None in value: - value.remove(None) - - if len(value) == 0: - return [] - - for arg in value: - if isinstance(arg, int): - arg = str(arg) - - list_.append({"value": arg, "input": argument["input"]}) - else: - raise WebScriptsArgumentError( - "Argument type must be: list, str, float, int, bool or None." - f" Not {type(value)} ({value})" - ) - - return list_ - - -class ScriptConfig(DefaultNamespace): - """ - This class makes script configurations. - """ - - __required__ = ["name", "dirname"] - __defaults__ = { - "documentation_content_type": "text/html", - "command_generate_documentation": None, - "stderr_content_type": "text/plain", - "content_type": "text/plain", - "documentation_file": None, - "print_real_time": False, - "minimum_access": None, - "access_groups": None, - "access_users": None, - "no_password": False, - "description": None, - "launcher": None, - "timeout": None, - "category": "", - "path": "", - "args": [], - } - __optional__ = [ - "command_generate_documentation", - "documentation_content_type", - "stderr_content_type", - "documentation_file", - "minimum_access", - "access_groups", - "content_type", - "access_users", - "no_password", - "description", - "category", - "launcher", - "timeout", - "path", - "args", - ] - __types__ = { - "access_groups": List[int], - "access_users": List[int], - "minimum_access": int, - "no_password": bool, - "timeout": int, - } - - @log_trace - def build_args(self, configuration: Configuration): - """ - This function build Arguments from self.args: List[Dict[str, str]] - """ - - args = [] - for arg in self.args: - arg = Argument.default_build(**arg) - - javascript_section = arg.get("javascript_section") - if javascript_section is not None: - javascript_configuration = configuration.get( - javascript_section - ) - - if not isinstance(javascript_configuration, dict): - raise WebScriptsConfigurationError( - f'"{javascript_section}" doesn\'t exist or ' - "is not a javascript object (a dictionnary)" - ) - - arg.javascript_attributs = javascript_configuration - - args.append(arg) - - self.args = args - - @classmethod - @log_trace - def build_scripts_from_configuration( - cls, server_configuration: ServerConfiguration - ) -> Dict[str, ScriptConfig]: - """ - This function build scripts from server - configuration and configurations files. - """ - - scripts_config = cls.get_scripts_from_configuration( - server_configuration, server_configuration - ) - - json_scripts_config = getattr( - server_configuration, "json_scripts_config", [] - ) - ini_scripts_config = getattr( - server_configuration, "ini_scripts_config", [] - ) - server_configuration.configuration_files = configuration_files = ( - getattr(server_configuration, "configuration_files", {}) - ) - - for dirname_ in (lib_directory, current_directory): - for config_path in ini_scripts_config: - config_path = join(dirname_, normcase(config_path)) - - for config_filename in iglob(config_path): - if not check_file_permission( - server_configuration, config_filename - ): - continue - - configuration = DefaultNamespace() - temp_configurations = get_ini_dict(config_filename) - configuration.update(**temp_configurations) - - scripts_config.update( - cls.get_scripts_from_configuration( - configuration, server_configuration - ) - ) - configuration_files[get_real_path(config_filename)] = ( - temp_configurations - ) - - for config_path in json_scripts_config: - config_path = join(dirname_, normcase(config_path)) - - for config_filename in iglob(config_path): - if not check_file_permission( - server_configuration, config_filename - ): - continue - - configuration = DefaultNamespace() - - with open(config_filename) as file: - temp_configurations = load(file) - configuration.update(**temp_configurations) - - scripts_config.update( - cls.get_scripts_from_configuration( - configuration, server_configuration - ) - ) - configuration_files[config_filename] = temp_configurations - - return scripts_config - - @classmethod - @log_trace - def get_scripts_from_configuration( - cls, - configuration: Configuration, - server_configuration: ServerConfiguration, - ) -> Dict[str, ScriptConfig]: - """ - This function build scripts from ServerConfiguration. - """ - - scripts = getattr(configuration, "scripts", {}) - scripts_config = {} - - for name, section_config in scripts.items(): - script_section = getattr(configuration, section_config, None) - logger_warning( - f"Script found: {name!r} (section: {section_config!r})" - ) - - if script_section is None: - raise WebScriptsConfigurationError( - f"section {section_config!r} doesn't exist (to configure " - f"script named {name!r})" - ) - else: - script_section = script_section.copy() - - ( - script_configuration, - script_section, - ) = cls.get_script_config_from_specific_file_config( - script_section, configuration, server_configuration - ) - - script_section["args"] = cls.get_arguments_from_config( - script_section.pop("args", None), script_configuration - ) - script_section["documentation_file"] = ( - cls.get_documentation_from_configuration( - script_section, - name, - getattr(server_configuration, "documentations_path", []), - ) - ) - script_section["name"] = name - - path_ = script_section.get("path") - script_section["path"] = script_path = get_real_path(path_) - if script_path is None: - script_path = script_section["path"] = cls.get_script_path( - server_configuration, script_section - ) - elif not isfile(script_path): - raise WebScriptsConfigurationError( - f"Location for script named {name!r} " - f"({script_path!r}) doesn't exist." - ) - - if script_section.get("launcher") is None: - script_section["launcher"] = ( - cls.get_Windows_default_script_launcher(script_section) - ) - - script_section["dirname"] = dirname(script_path) - - if not check_file_permission( - server_configuration, script_path, executable=True - ): - continue - - build = scripts_config[name] = cls.default_build(**script_section) - build.build_args(script_configuration) - - if path_ is None: - build.path_is_defined = False - else: - build.path_is_defined = True - - return scripts_config - - @staticmethod - @log_trace - def get_Windows_default_script_launcher( - script_config: Dict[str, JsonValue] - ) -> str: - """ - This function gets the Windows default launcher to execute a file. - """ - - if not IS_WINDOWS: - return None - - logger_info( - f"Research default launcher for script {script_config['name']}" - ) - extension = splitext(script_config["path"])[1] - - if ( - fullmatch(r"[.]\w+", extension) is None - ): # raise an error against command injection - logger_critical( - f'Security Error: this extension "{extension}" is a security ' - "risk (for security reason this extension is blocked)." - ) - raise WebScriptsSecurityError( - f"Invalid extension: {extension} (for security " - "reason this extension is blocked)" - ) - - command = [ - r"C:\WINDOWS\system32\cmd.exe", - "/c", - "assoc", - extension, - ] - logger_command("Get launcher from extension: " + repr(command)) - - process = Popen( - command, # protection against command injection - stdout=PIPE, - stderr=PIPE, - text=True, - ) # nosec - stdout, stderr = process.communicate() - - if process.returncode != 0: - return None - filetype = stdout.split("=")[1] if "=" in stdout else "" - - command = [r"C:\WINDOWS\system32\cmd.exe", "/c", "ftype", filetype] - logger_command("Get launcher from extension: " + repr(command)) - - process = Popen( - command, - stdout=PIPE, - stderr=PIPE, - text=True, - ) # nosec - stdout, stderr = process.communicate() - - if process.returncode != 0: - return None - launcher = ( - stdout.split()[0].split("=")[1].replace('"', "") - if "=" in stdout - else None - ) - - if launcher is not None: - logger_warning( - f"Launcher found for {script_config['name']}: {launcher}" - ) - - return launcher - - @staticmethod - @log_trace - def get_script_path( - server_configuration: ServerConfiguration, - script_config: Dict[str, JsonValue], - ) -> str: - """ - This function return a script path from configuration. - """ - - for dirname_ in (lib_directory, current_directory): - for directory in server_configuration.scripts_path: - script_path = join( - dirname_, normcase(directory), script_config["name"] - ) - if isfile(script_path): - logger_info( - f"Found script named: {script_config['name']} in " - f"location: {script_path}" - ) - return script_path - - raise WebScriptsConfigurationError( - f"No location found for script named {script_config['name']}" - ) - - @staticmethod - @log_trace - def get_documentation_from_configuration( - script_config: Dict[str, JsonValue], name: str, paths: List[str] - ) -> str: - """ - This function get documentation from script configuration - or search it in documentation path. - """ - - doc_file = script_config.get("documentation_file") - - if doc_file is None: - for path_ in paths: - doc_files = join(path_, f"{splitext(basename(name))[0]}.*") - for doc_file in iglob(doc_files): - logger_debug(f"Documentation file found for {name}") - break - else: - logger_debug(f"Documentation file found for {name}") - - return doc_file - - @staticmethod - @log_trace - def get_arguments_from_config( - arguments_section: str, configuration: Dict[str, Dict[str, JsonValue]] - ) -> List[Dict[str, JsonValue]]: - """ - This function get arguments list of script. - """ - - if arguments_section is not None: - args_config = configuration.get(arguments_section) - - if args_config is None: - raise WebScriptsConfigurationError( - f"{arguments_section} " - "section doesn't exist in configuration" - ) - - arguments_config = [] - for name, arg_section in args_config.items(): - arg_config = configuration.get(arg_section) - - if arg_config is None: - raise WebScriptsConfigurationError( - f"{arg_section} section doesn't exist " - f'in configuration (for argument named "{name}")' - ) - - arg_config["name"] = name - arguments_config.append(arg_config) - - logger_info(f"Argument named {name} found and configured.") - else: - arguments_config = [] - - return arguments_config - - @staticmethod - @log_trace - def get_script_config_from_specific_file_config( - script_config: Dict[str, JsonValue], - configuration: Dict[str, JsonValue], - server_configuration: Dict[str, JsonValue], - ) -> Tuple[dict, dict]: - """ - This function return all configuration and - script configuration from configuration. - """ - - configuration_file = script_config.get("configuration_file") - - if configuration_file is not None: - if configuration_file.endswith(".json"): - file = get_file_content(configuration_file, as_iterator=True) - configuration = load(file) - file.close() - else: - config = ConfigParser( - allow_no_value=True, inline_comment_prefixes="#" - ) - config.read(configuration_file) - configuration = config._sections - - script_config = configuration.get("script") - - if script_config is None: - raise WebScriptsConfigurationError( - f'Section "script" is not defined in {configuration_file}' - ) - - server_configuration.configuration_files = configuration_files = ( - getattr(server_configuration, "configuration_files", {}) - ) - configuration_files[get_real_path(configuration_file)] = ( - configuration - ) - - return configuration, script_config - - @log_trace - def get_JSON_API(self) -> Dict: - """ - This function return a dict for JSON API - (visible configuration for user). - """ - - json_api = self.get_dict() - - for key in ( - "command_generate_documentation", - "documentation_file", - "print_real_time", - "minimum_access", - "access_groups", - "access_users", - "no_password", - "launcher", - "timeout", - "dirname", - "path", - ): - del json_api[key] - - if "secrets" in json_api.keys(): - del json_api["secrets"] - - arguments = json_api.pop("args") - json_api["args"] = [] - - for argument in arguments: - json_api["args"].append(argument.get_dict()) - - return json_api - - @staticmethod - @log_trace - def get_docfile_from_configuration( - configuration: ServerConfiguration, filename: str - ) -> str: - """ - This method returns the script documentation - path if it exists. - """ - - for dirname_ in (lib_directory, current_directory): - for doc_glob in configuration.documentations_path: - doc_glob = join(dirname_, normcase(doc_glob)) - for doc in iglob(doc_glob): - doc_dirname, doc_filename = split(doc) - no_extension, extension = splitext(doc_filename) - - if no_extension == splitext(basename(filename))[0]: - return doc - - -class User(DefaultNamespace): - """ - This class implements User object. - """ - - __required__ = [ - "id", - "name", - "groups", - "csrf", - "ip", - "categories", - "scripts", - "check_csrf", - ] - __types__ = { - "id": int, - "check_csrf": bool, - "groups": List[int], - "scripts": list, - "categories": list, - } - __defaults__ = { - "csrf": {}, - "groups": [], - "scripts": ["*"], - "categories": ["*"], - "check_csrf": False, - } - - -class CallableFile(Callable): - """ - This class build callable object to return - Web files content or script output. - """ - - template_script_path: str = get_real_path("static/templates/script.html") - template_header_path: str = get_real_path("static/templates/header.html") - template_footer_path: str = get_real_path("static/templates/footer.html") - template_index_path: str = get_real_path("static/templates/index.html") - - template_script: str = get_file_content(template_script_path) - template_header: str = get_file_content(template_header_path) - template_footer: str = get_file_content(template_footer_path) - template_index: str = get_file_content(template_index_path) - - @log_trace - def __init__( - self, - type_: str, - path_: str, - filename: str, - config: dict = None, - security: bool = True, - ): - self.path = path_ - self.type = type_ - self.config = config - self.security = security - self.filename = filename - self.extension = splitext(path_)[1].lower() - - @log_trace - def __call__( - self, user: User, subdirectory: int = 2 - ) -> Tuple[str, Dict[str, str], List[bytes]]: - if self.type == "js": - return ( - "200 OK", - {"Content-Type": "text/javascript; charset=utf-8"}, - get_file_content(self.path, as_iterator=True), - ) - elif self.type == "static": - if self.is_html(): - return ( - "200 OK", - {"Content-Type": "text/html; charset=utf-8"}, - get_file_content(self.path, as_iterator=True), - ) - elif self.extension == ".css": - return ( - "200 OK", - {"Content-Type": "text/css; charset=utf-8"}, - get_file_content(self.path, as_iterator=True), - ) - elif self.extension == ".ico": - return ( - "200 OK", - {"Content-Type": "image/x-icon"}, - get_file_content(self.path, as_iterator=True), - ) - elif self.extension == ".png": - return ( - "200 OK", - {"Content-Type": "image/png"}, - get_file_content(self.path, as_iterator=True), - ) - elif self.is_jpeg(): - return ( - "200 OK", - {"Content-Type": "image/jpeg"}, - get_file_content(self.path, as_iterator=True), - ) - elif self.extension == ".gif": - return ( - "200 OK", - {"Content-Type": "image/gif"}, - get_file_content(self.path, as_iterator=True), - ) - elif self.extension == ".json": - return ( - "200 OK", - {"Content-Type": "application/json; charset=utf-8"}, - get_file_content(self.path, as_iterator=True), - ) - elif self.extension == ".txt": - return ( - "200 OK", - {"Content-Type": "text/plain; charset=utf-8"}, - get_file_content(self.path, as_iterator=True), - ) - elif self.extension == ".pdf": - return ( - "200 OK", - {"Content-Type": "application/pdf; charset=utf-8"}, - get_file_content(self.path, as_iterator=True), - ) - elif self.extension == ".csv": - return ( - "200 OK", - {"Content-Type": "text/csv; charset=utf-8"}, - get_file_content(self.path, as_iterator=True), - ) - elif self.is_tiff(): - return ( - "200 OK", - {"Content-Type": "image/tiff"}, - get_file_content(self.path, as_iterator=True), - ) - elif self.is_xml(): - return ( - "200 OK", - {"Content-Type": "application/xml; charset=utf-8"}, - get_file_content(self.path, as_iterator=True), - ) - elif self.extension == ".svg": - return ( - "200 OK", - {"Content-Type": "image/svg+xml"}, - get_file_content(self.path, as_iterator=True), - ) - else: - return ( - "200 OK", - {"Content-Type": "application/octet-stream"}, - get_file_content(self.path, as_iterator=True), - ) - elif self.type == "script": - nonce = token_hex(20) - subpath = "../" * subdirectory - template = CallableFile.template_script.replace( - 'src="../js/', 'src="' + subpath + "js/" - ).replace('href="../static/', 'href="' + subpath + "static/") - return ( - "200 OK", - { - "Content-Type": "text/html; charset=utf-8", - ( - "Content-Security-Policy" - if self.security - else "Content-Security-Policy-Report-Only" - ): ( - "default-src 'self'; navigate-to 'self'; worker-src " - "'none'; style-src-elem 'self'; style-src-attr 'none';" - " style-src 'self'; script-src-attr 'none'; object-src" - " 'none'; media-src 'none'; manifest-src 'none'; " - "frame-ancestors 'none'; connect-src 'self'; font-src" - " 'none'; img-src 'self'; base-uri 'none'; child-src" - " 'none'; form-action 'none'; script-src 'self' " - f"'nonce-{nonce}' 'require-trusted-types-for'" - ), - }, - ( - template - if self.security - else template.replace( - "Content-Security-Policy", - "Content-Security-Policy-Report-Only", - ) - ) - % { - "name": self.filename, - "user": user.name, - "csrf": TokenCSRF.build_token(user), - "nonce": nonce, - "header": self.template_header.replace( - 'href="../web/', 'href="' + subpath + "web/" - ), - "footer": self.template_footer.replace( - 'href="../static/', 'href="' + subpath + "static/" - ), - "subpath": subpath, - }, - ) - - def is_xml(self) -> bool: - """ - This function compare extension with xml extensions. - """ - - return self.extension in ( - ".xml", - ".xsd", - ".xslt", - ".tld", - ".dtml", - ".rss", - ".opml", - ) - - def is_html(self) -> bool: - """ - This function compare extension with html extensions. - """ - - return self.extension in (".html", ".htm", ".shtml", ".xhtml") - - def is_jpeg(self) -> bool: - """ - This function compare extension with jpeg extensions. - """ - - return self.extension in (".jpg", ".jpeg", ".jpe") - - def is_tiff(self) -> bool: - """ - This function compare extension with tif extensions. - """ - - return self.extension in (".tiff", ".tif") - - -class Blacklist: - """ - This class implement blacklist. - """ - - def __init__( - self, - configuration: ServerConfiguration, - last_blacklist: Blacklist = None, - ): - logger_debug("New blacklist object...") - self.time = time() - - blacklist_time = getattr(configuration, "blacklist_time", None) - - if blacklist_time is None: - self.counter = 1 - logger_debug("Counter initialized, cause: no blacklist time.") - else: - if last_blacklist is None: - self.counter = 1 - logger_debug("Counter initialized, cause: no blacklist.") - else: - if ( - last_blacklist.time + configuration.blacklist_time - >= self.time - ): - counter = self.counter = last_blacklist.counter + 1 - logger_info(f"Counter increment: {counter}.") - else: - self.counter = 1 - logger_debug( - "Counter initialized, cause: blacklist time exceeded." - ) - - @log_trace - def is_blacklist(self, configuration: ServerConfiguration) -> bool: - """ - This function return True if this object is blacklisted. - """ - - blacklist_time = getattr(configuration, "blacklist_time", None) - auth_failures_to_blacklist = getattr( - configuration, "auth_failures_to_blacklist", None - ) - - if auth_failures_to_blacklist is None: - logger_debug( - "Not blacklisted, cause: configuration " - '"auth_failures_to_blacklist" is None.' - ) - return False - - if self.counter > auth_failures_to_blacklist: - if blacklist_time is None: - logger_debug("Not blacklisted, cause: blacklist time is None") - return False - - is_blacklisted = blacklist_time + self.time >= time() - logger_info(f"Blacklist state: {is_blacklisted}") - return is_blacklisted - else: - logger_debug( - "Not blacklisted, cause: counter less than " - 'configuration "auth_failures_to_blacklist".' - ) - return False - - def __str__(self) -> str: - """ - This function returns a string to represent the Blacklist object. - """ - - return ( - f"Blacklist(counter={self.counter}, " - f"blacklist_time={time() - self.time})" - ) - - -class TokenCSRF: - """ - This class brings together the functions related to the CSRF token - """ - - @staticmethod - @log_trace - def build_token(user: User) -> str: - """ - This function build a CSRF token for a user. - """ - - token = b64encode(token_bytes(48)).decode() - user.csrf[token] = time() - return token - - @staticmethod - @log_trace - def check_csrf( - user: User, - token: str, - csrf_max_time: float = 300, - referer: str = None, - baseurl: str = None, - ) -> bool: - """ - This function check the validity of a csrf token. - """ - - max_time = time() - csrf_max_time - - if ( - referer - and baseurl - and not referer.lstrip("htps").startswith(baseurl.lstrip("htps")) - ): - logger_error( - f"Referrer error: referer ({referer!r}) " - f"do not start with baseurl ({baseurl!r})." - ) - TokenCSRF.clean(user, max_time) - return False - - timestamp = user.csrf.pop(token, 0) - - if timestamp >= max_time: - return True - else: - logger_warning( - f"CSRF Token has expired ({timestamp} >= {max_time})" - ) - TokenCSRF.clean(user, max_time) - return False - - @staticmethod - @log_trace - def clean(user: User, max_time: float) -> None: - """ - This function clean all old CSRF tokens for a user. - """ - - to_delete = [] - - for token, timestamp in user.csrf.items(): - if timestamp <= max_time: - to_delete.append(token) - - for key in to_delete: - del user.csrf[key] - - -class Session: - """ - Object to implement session. - """ - - def __init__(self, user: User, ip: str): - self.cookie = token_hex(64) - self.time = time() - self.user = user - self.ip = ip - - def __str__(self) -> str: - """ - This function returns a string to represent the Session object. - """ - - return ( - f"Session(Time={time() - self.time}, IP={self.ip}, " - f"Cookie={self.cookie}, User={self.user})" - ) - - @classmethod - @log_trace - def build_session(cls, user: User, ip: str, Pages: Pages) -> str: - """ - This function build and add session and return the cookie. - """ - - session: Session = cls(user, ip) - Pages.sessions[user.id] = session - return f"{user.id}:{session.cookie}" - - @staticmethod - @log_trace - def check_session( - cookie: str, - pages: Pages, - ip: str, - default_user: User, - session_max_time: float = 3600, - ) -> User: - """ - This function check session validity and return user. - """ - - if cookie.startswith("SessionID="): - cookie = cookie[10:] - else: - logger_error("Session cookie do not start with 'SessionID='.") - return default_user - - if ":" in cookie: - user_id, cookie_session = cookie.split(":", 1) - else: - logger_error("Invalid session cookie: ':' not in cookie") - return default_user - - if user_id.isdigit(): - session = pages.sessions.get(int(user_id), None) - else: - logger_error("Cookie: UserID is not digit.") - return default_user - - if session is None: - logger_info("Session not found.") - return default_user - - if ( - session.ip == ip - and session.time + session_max_time >= time() - and session.cookie == cookie_session - ): - return session.user - else: - logger_warning("Session: Bad IP or session expired or bad cookie.") - return default_user diff --git a/config/files/change_my_password.json b/config/files/change_my_password.json deleted file mode 100644 index 224b5744..00000000 --- a/config/files/change_my_password.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "script": { - "timeout": 15, - "launcher": "/usr/bin/python3", - "minimum_access": 50, - "category": "My Account", - "args": "change_my_password_args", - "description": "This script can change your own password (for all authenticated users).", - "command_generate_documentation": "python \"%(dirname)s/../doc/py_doc.py\" \"%(path)s\"", - "path": "./scripts/account/change_my_password.py" - }, - "change_my_password_args": { - "old_password": "arg_old_password", - "password": "arg_password", - "password_confirmation": "arg_confirm_password" - }, - "arg_old_password": { - "example": "password", - "html_type": "password", - "description": "Your current password" - }, - "arg_password": { - "example": "password", - "html_type": "password", - "description": "New password value." - }, - "arg_confirm_password": { - "example": "password", - "html_type": "password", - "description": "New password value (confirmation)." - } -} diff --git a/config/files/test_config.json b/config/files/test_config.json deleted file mode 100644 index 54b67c9e..00000000 --- a/config/files/test_config.json +++ /dev/null @@ -1,75 +0,0 @@ -{ - "script": { - "timeout": 25, - "args": "arguments", - "no_password": true, - "launcher": "/usr/bin/python3", - "path": "./scripts/py/test_config.py", - "description": "This script test interrface, arguments and script configurations.", - "print_real_time": true - }, - "arguments": { - "select": "select", - "--timeout": "timeout", - "password": "password", - "--test-date": "--test-date", - "test_file": "test_file", - "test_input": "test_input", - "test_number": "test_number", - "select-input": "select-input" - }, - "timeout": { - "html_type": "checkbox", - "description": "Test checkbox and the process timeout and optional argument without value." - }, - "select": { - "example": "password", - "html_type": "password", - "description": "Test select (listbox).", - "default_value": "select", - "predefined_values": [ - "", - "test", - "select", - "arguments" - ] - }, - "password": { - "list": true, - "example": "password", - "html_type": "password", - "description": "Test password and list of values for one argument." - }, - "test_input": { - "example": "input", - "description": "Test HTML input as argument." - }, - "--test-date": { - "html_type": "date", - "description": "Test HTML input type date and optional argument with value." - }, - "test_file": { - "html_type": "file", - "input": true, - "description": "Test HTML input type file." - }, - "test_number": { - "html_type": "number", - "description": "Test HTML input type number with specific javascript configurations.", - "javascript_section": "js_section_number" - }, - "js_section_number": { - "step": "0.002" - }, - "select-input": { - "list": true, - "input": true, - "predefined_values": [ - "", - "test", - "select", - "arguments" - ], - "description": "Test select with multiples selected values as input." - } -} diff --git a/config/loggers.ini b/config/loggers.ini deleted file mode 100644 index ff8565fc..00000000 --- a/config/loggers.ini +++ /dev/null @@ -1,194 +0,0 @@ -[loggers] -keys=root,WebScripts.console,WebScripts.file,WebScripts.debug,WebScripts.info,WebScripts.warning,WebScripts.error,WebScripts.critical,WebScripts.trace,WebScripts.authentication,WebScripts.access,WebScripts.response,WebScripts.command - -[handlers] -keys=root,console,file,debug,info,warning,error,critical,trace,authentication,access,response,command - -[formatters] -keys=specificlevel,basic,root - -[logger_root] -level=NOTSET -handlers=root -propagate=0 -formatter=root - -[logger_WebScripts.authentication] -level=NOTSET -handlers=authentication -qualname=WebScripts.authentication -formatter=specificlevel -propagate=0 - -[logger_WebScripts.access] -level=NOTSET -handlers=access -qualname=WebScripts.access -formatter=specificlevel -propagate=0 - -[logger_WebScripts.command] -level=NOTSET -handlers=command -qualname=WebScripts.command -formatter=specificlevel -propagate=0 - -[logger_WebScripts.response] -level=NOTSET -handlers=response -qualname=WebScripts.response -formatter=specificlevel -propagate=0 - -[logger_WebScripts.console] -level=NOTSET -handlers=console -qualname=WebScripts.console -formatter=basic -propagate=0 - -[logger_WebScripts.file] -level=NOTSET -handlers=file -qualname=WebScripts.file -formatter=basic -propagate=0 - -[logger_WebScripts.debug] -level=DEBUG -handlers=debug -qualname=WebScripts.debug -formatter=specificlevel -propagate=0 - -[logger_WebScripts.info] -level=INFO -handlers=info -qualname=WebScripts.info -formatter=specificlevel -propagate=0 - -[logger_WebScripts.warning] -level=WARNING -handlers=warning -qualname=WebScripts.warning -formatter=specificlevel -propagate=0 - -[logger_WebScripts.error] -level=ERROR -handlers=error -qualname=WebScripts.error -formatter=specificlevel -propagate=0 - -[logger_WebScripts.critical] -level=CRITICAL -handlers=critical -qualname=WebScripts.critical -formatter=specificlevel -propagate=0 - -[logger_WebScripts.trace] -level=NOTSET -handlers=trace -qualname=WebScripts.trace -formatter=specificlevel -propagate=0 - -[formatter_root] -format=[%(asctime)s] %(levelname)-9s(%(levelno)s) {%(name)s - %(filename)s:%(lineno)d} %(message)s -datefmt=%Y-%m-%d %H:%M:%S -class=logging.Formatter - -[formatter_basic] -format=[%(asctime)s] %(levelname)-9s(%(levelno)s) {%(filename)s:%(lineno)d} %(message)s -datefmt=%Y-%m-%d %H:%M:%S -class=logging.Formatter - -[formatter_specificlevel] -format=[%(asctime)s] {%(filename)s:%(lineno)d} %(message)s -datefmt=%Y-%m-%d %H:%M:%S -class=logging.Formatter - -[handler_root] -class=handlers.CustomLogHandler -level=NOTSET -formatter=root -args=("logs/root.logs", "a", 10485760, 10485760,) - -[handler_console] -class=StreamHandler -level=NOTSET -formatter=basic -args=(sys.stdout,) - -[handler_authentication] -class=handlers.CustomLogHandler -level=NOTSET -formatter=specificlevel -args=("logs/24-auth.logs", "a", 10485760, 10485760,) - -[handler_access] -class=handlers.CustomLogHandler -level=NOTSET -formatter=specificlevel -args=("logs/25-access.logs", "a", 10485760, 10485760,) - -[handler_response] -class=handlers.CustomLogHandler -level=NOTSET -formatter=specificlevel -args=("logs/26-response.logs", "a", 10485760, 10485760,) - -[handler_command] -class=handlers.CustomLogHandler -level=NOTSET -formatter=specificlevel -args=("logs/27-command.logs", "a", 10485760, 10485760,) - -[handler_file] -class=handlers.CustomLogHandler -level=NOTSET -formatter=basic -args=("logs/00-server.logs", "a", 10485760, 10485760,) - -[handler_debug] -class=handlers.CustomLogHandler -level=DEBUG -formatter=specificlevel -args=("logs/10-debug.logs", "a", 10485760, 10485760,) - -[handler_info] -class=handlers.CustomLogHandler -level=INFO -formatter=specificlevel -args=("logs/20-info.logs", "a", 10485760, 10485760,) - -[handler_warning] -class=handlers.CustomLogHandler -level=WARNING -formatter=specificlevel -args=("logs/30-warning.logs", "a", 10485760, 10485760,) - -[handler_error] -class=handlers.CustomLogHandler -level=ERROR -formatter=specificlevel -args=("logs/40-error.logs", "a", 10485760, 10485760,) - -[handler_critical] -class=handlers.CustomLogHandler -filename=logs/critical.logs -level=CRITICAL -formatter=specificlevel -args=("logs/50-critical.logs", "a", 10485760, 10485760,) - -[handler_trace] -class=handlers.CustomLogHandler -filename=logs/trace.logs -level=NOTSET -formatter=specificlevel -args=("logs/05-trace.logs", "a", 10485760, 10485760,) - diff --git a/config/nt/files/change_my_password.ini b/config/nt/files/change_my_password.ini deleted file mode 100644 index ca82d358..00000000 --- a/config/nt/files/change_my_password.ini +++ /dev/null @@ -1,28 +0,0 @@ -[script] -timeout=15 -launcher=python -minimum_access=50 -category=My Account -args=change_my_password_args -description=This script can change your own password (for all authenticated users). -command_generate_documentation=python "%(dirname)s/../doc/py_doc.py" "%(path)s" - -[change_my_password_args] -old_password=arg_old_password -password=arg_password -password_confirmation=arg_confirm_password - -[arg_old_password] -example=password -html_type=password -description=Your current password - -[arg_password] -example=password -html_type=password -description=New password value. - -[arg_confirm_password] -example=password -html_type=password -description=New password value (confirmation). diff --git a/config/nt/files/change_my_password.json b/config/nt/files/change_my_password.json deleted file mode 100644 index e3221b01..00000000 --- a/config/nt/files/change_my_password.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "script": { - "timeout": 15, - "launcher": "python", - "minimum_access": 50, - "category": "My Account", - "args": "change_my_password_args", - "description": "This script can change your own password (for all authenticated users).", - "command_generate_documentation": "python \"%(dirname)s/../doc/py_doc.py\" \"%(path)s\"" - }, - - "change_my_password_args": { - "old_password": "arg_old_password", - "password": "arg_password", - "password_confirmation": "arg_confirm_password" - }, - - "arg_old_password": { - "example": "password", - "html_type": "password", - "description": "Your current password" - }, - - "arg_password": { - "example": "password", - "html_type": "password", - "description": "New password value." - }, - - "arg_confirm_password": { - "example": "password", - "html_type": "password", - "description": "New password value (confirmation)." - } -} \ No newline at end of file diff --git a/config/nt/files/test_config.json b/config/nt/files/test_config.json deleted file mode 100644 index d92e3156..00000000 --- a/config/nt/files/test_config.json +++ /dev/null @@ -1,75 +0,0 @@ -{ - "script": { - "timeout": 25, - "args": "arguments", - "no_password": true, - "launcher": "python", - "path": "./scripts/py/test_config.py", - "description": "This script test interrface, arguments and script configurations.", - "print_real_time": true - }, - - "arguments": { - "select": "select", - "--timeout": "timeout", - "password": "password", - "--test-date": "--test-date", - "test_file": "test_file", - "test_input": "test_input", - "test_number": "test_number", - "select-input": "select-input" - }, - - "timeout": { - "html_type": "checkbox", - "description": "Test checkbox and the process timeout and optional argument without value." - }, - - "select": { - "example": "password", - "html_type": "password", - "description": "Test select (listbox).", - "default_value": "select", - "predefined_values": ["", "test", "select", "arguments"] - }, - - "password": { - "list": true, - "example": "password", - "html_type": "password", - "description": "Test password and list of values for one argument." - }, - - "test_input": { - "example": "input", - "description": "Test HTML input as argument." - }, - - "--test-date": { - "html_type": "date", - "description": "Test HTML input type date and optional argument with value." - }, - - "test_file": { - "html_type": "file", - "input": true, - "description": "Test HTML input type file." - }, - - "test_number": { - "html_type": "number", - "description": "Test HTML input type number with specific javascript configurations.", - "javascript_section": "js_section_number" - }, - - "js_section_number": { - "step": "0.002" - }, - - "select-input": { - "list": true, - "input": true, - "predefined_values": ["", "test", "select", "arguments"], - "description": "Test select with multiples selected values as input." - } -} \ No newline at end of file diff --git a/config/nt/scripts/default_admin_scripts.json b/config/nt/scripts/default_admin_scripts.json deleted file mode 100644 index de48d1a7..00000000 --- a/config/nt/scripts/default_admin_scripts.json +++ /dev/null @@ -1,274 +0,0 @@ -{ - "scripts": { - "add_user.py": "config_add_user", - "add_group.py": "config_add_group", - "view_users.py": "config_view_users", - "get_apikey.py": "config_get_apikey", - "view_groups.py": "config_view_groups", - "delete_user.py": "config_delete_user", - "delete_group.py": "config_delete_group", - "api_view_users.py": "config_api_view_users", - "api_view_groups.py": "config_api_view_groups", - "change_user_password.py": "config_change_user_password", - "my_user_informations.py": "config_my_user_informations" - }, - - "config_change_user_password": { - "timeout": 15, - "launcher": "python", - "access_groups": [1000], - "category": "Administration", - "args": "args_change_user_password", - "description": "This script reset a user password (for admin only)", - "command_generate_documentation": "python \"%(dirname)s/../doc/py_doc.py\" \"%(path)s\"" - }, - - "args_change_user_password": { - "user_id": "arg_id", - "password": "arg_password" - }, - - "config_delete_user": { - "timeout": 15, - "access_users": [], - "no_password": true, - "launcher": "python", - "access_groups": [1000], - "content_type": "text/plain", - "category": "Administration", - "args": "config_delete_user_args", - "documentation_content_type": "text/html", - "path": "./scripts/account/delete_user.py", - "documentation_file": "./doc/delete_user.html", - "description": "This script delete user from ID.", - "command_generate_documentation": "python \"%(dirname)s/../doc/py_doc.py\" \"%(path)s\"" - }, - - "config_delete_user_args": { - "id": "arg_id" - }, - - "arg_id": { - "list": false, - "input": false, - "example": "55", - "html_type": "number", - "default_value": null, - "predefined_values": null, - "description": "User ID (must be unique)" - }, - - "config_add_user": { - "timeout": 15, - "launcher": "python", - "access_groups": [1000], - "content_type": "text/plain", - "category": "Administration", - "args": "config_add_user_args", - "description": "This script add a new user.", - "command_generate_documentation": "python \"%(dirname)s/../doc/py_doc.py\" \"%(path)s\"" - - }, - - "config_add_user_args": { - "username": "arg_username", - "password": "arg_password", - "--ips": "arg_ips", - "--groups": "arg_group_ids", - "--group-names": "arg_group_names", - "--categories": "arg_categories", - "--scripts": "arg_scripts" - }, - - "arg_username": { - "list": false, - "example": "user", - "html_type": "text", - "description": "Username for user (must be unique)" - }, - - "arg_group_ids": { - "list": true, - "example": "50", - "html_type": "number", - "description": "List of groups IDs to add permissions to the new user." - }, - - "arg_group_names": { - "list": true, - "example": "User", - "description": "List of groups names to add permissions to the new user." - }, - - "arg_ips": { - "list": true, - "html_type": "text", - "example": "127.0.*", - "description": "List of glob syntax for authorized IPs." - }, - - "arg_password": { - "example": "password", - "html_type": "password", - "description": "The user password" - }, - - "arg_scripts": { - "list": true, - "is_advanced": true, - "example": "antivirus*.py", - "description": "List of glob syntax for authorized scripts." - }, - - "arg_categories": { - "list": true, - "is_advanced": true, - "example": "Admin*", - "description": "List of glob syntax for authorized categories." - }, - - "config_view_users": { - "timeout": 15, - "no_password": true, - "launcher": "python", - "access_groups": [1000], - "args": "view_users_args", - "content_type": "text/csv", - "category": "Administration", - "description": "This script list all users to get names, IDs and groups (access level).", - "command_generate_documentation": "python \"%(dirname)s/../doc/py_doc.py\" \"%(path)s\"" - }, - - "config_api_view_users": { - "timeout": 15, - "no_password": true, - "launcher": "python", - "access_groups": [1000], - "args": "view_users_args", - "content_type": "text/json", - "category": "Administration", - "description": "This script list all users to get names, IDs and groups (access level).", - "command_generate_documentation": "python \"%(dirname)s/../doc/py_doc.py\" \"%(path)s\"" - }, - - "view_users_args": { - "--ids": "arg_ids", - "--names": "arg_names" - }, - - "arg_ids": { - "list": true, - "example": "5", - "html_type": "number", - "description": "List of IDs to display them only." - }, - - "arg_names": { - "list": true, - "example": "user", - "html_type": "text", - "description": "List of names to display them only." - }, - - "config_add_group": { - "timeout": 15, - "no_password": true, - "launcher": "python", - "access_groups": [1000], - "args": "add_group_args", - "category": "Administration", - "description": "This script can add group (to define new access level).", - "command_generate_documentation": "python \"%(dirname)s/../doc/py_doc.py\" \"%(path)s\"" - }, - - "add_group_args": { - "name": "arg_group_name", - "access_level": "arg_group_level" - }, - - "arg_group_level": { - "example": "group", - "html_type": "text", - "description": "Name of the new group (must be unique)." - }, - - "arg_group_name": { - "example": "1000", - "html_type": "number", - "description": "Level of the new group (must be unique)." - }, - - "config_delete_group": { - "timeout": 15, - "no_password": true, - "launcher": "python", - "access_groups": [1000], - "args": "delete_group_args", - "category": "Administration", - "description": "This script can delete group from ID.", - "command_generate_documentation": "python \"%(dirname)s/../doc/py_doc.py\" \"%(path)s\"" - }, - - "delete_group_args": { - "ID": "arg_group_id" - }, - - "arg_group_id": { - "example": "5", - "html_type": "number", - "description": "Group ID to delete (you can get it with view_groups)." - }, - - "config_view_groups": { - "timeout": 15, - "no_password": true, - "launcher": "python", - "access_groups": [1000], - "args": "view_groups_args", - "content_type": "text/csv", - "category": "Administration", - "description": "This script list all groups to get IDs, access level and names.", - "command_generate_documentation": "python \"%(dirname)s/../doc/py_doc.py\" \"%(path)s\"" - }, - - "config_api_view_groups": { - "timeout": 15, - "no_password": true, - "launcher": "python", - "access_groups": [1000], - "args": "view_groups_args", - "content_type": "text/json", - "category": "Administration", - "description": "This script list all groups to get IDs, access level and names.", - "command_generate_documentation": "python \"%(dirname)s/../doc/py_doc.py\" \"%(path)s\"" - }, - - "view_groups_args": { - "--ids": "arg_ids", - "--names": "arg_names" - }, - - "config_get_apikey": { - "timeout": 15, - "launcher": "python", - "minimum_access": 50, - "category": "My Account", - "args": "args_get_apikey", - "description": "This script print the API key of the current user.", - "command_generate_documentation": "python \"%(dirname)s/../doc/py_doc.py\" \"%(path)s\"" - }, - - "args_get_apikey": { - "password": "arg_password" - }, - - "config_my_user_informations": { - "timeout": 15, - "no_password": true, - "minimum_access": 50, - "launcher": "python", - "category": "My Account", - "description": "This script prints user informations.", - "command_generate_documentation": "python \"%(dirname)s/../doc/py_doc.py\" \"%(path)s\"" - } -} \ No newline at end of file diff --git a/config/nt/scripts/default_log_scripts.json b/config/nt/scripts/default_log_scripts.json deleted file mode 100644 index 49ccb000..00000000 --- a/config/nt/scripts/default_log_scripts.json +++ /dev/null @@ -1,100 +0,0 @@ -{ - "scripts": { - "log_viewer.py": "config_log_viewer", - "log_analysis.py": "config_log_analysis" - }, - - "config_log_analysis": { - "timeout": 120, - "no_password": true, - "launcher": "python", - "category": "Security", - "minimum_access": 1000, - "content_type": "text/csv", - "path": "./scripts/logs/log_analysis.py", - "description": "This file displays an HTML table for log and activity analysis.", - "command_generate_documentation": "python \"%(dirname)s/../doc/py_doc.py\" \"%(path)s\"" - }, - - "config_log_viewer": { - "timeout": 30, - "no_password": true, - "launcher": "python", - "category": "Security", - "minimum_access": 1000, - "args": "args_log_viewer", - "path": "./scripts/logs/log_viewer.py", - "description": "This file can display the latest logs.", - "command_generate_documentation": "python \"%(dirname)s/../doc/py_doc.py\" \"%(path)s\"" - }, - - "args_log_viewer": { - "length": "arg_length", - "all": "arg_all", - "INFO": "arg_INFO", - "DEBUG": "arg_DEBUG", - "ERROR": "arg_ERROR", - "TRACE": "arg_TRACE", - "ACCESS": "arg_ACCESS", - "COMMAND": "arg_COMMAND", - "WARNING": "arg_WARNING", - "RESPONSE": "arg_RESPONSE", - "CRITICAL": "arg_CRITICAL" - }, - - "arg_length": { - "default_value": 10, - "html_type": "number", - "description": "Number of logs to print." - }, - - "arg_all": { - "html_type": "checkbox", - "description": "View the latest logs." - }, - - "arg_TRACE": { - "html_type": "checkbox", - "description": "View the latest TRACE logs." - }, - - "arg_DEBUG": { - "html_type": "checkbox", - "description": "View the latest DEBUG logs." - }, - - "arg_INFO": { - "html_type": "checkbox", - "description": "View the latest INFO logs." - }, - - "arg_ACCESS": { - "html_type": "checkbox", - "description": "View the latest ACCESS logs." - }, - - "arg_RESPONSE": { - "html_type": "checkbox", - "description": "View the latest RESPONSE logs." - }, - - "arg_COMMAND": { - "html_type": "checkbox", - "description": "View the latest COMMAND logs." - }, - - "arg_WARNING": { - "html_type": "checkbox", - "description": "View the latest WARNING logs." - }, - - "arg_ERROR": { - "html_type": "checkbox", - "description": "View the latest ERROR logs." - }, - - "arg_CRITICAL": { - "html_type": "checkbox", - "description": "View the latest CRITICAL logs." - } -} \ No newline at end of file diff --git a/config/nt/scripts/default_password_scripts.json b/config/nt/scripts/default_password_scripts.json deleted file mode 100644 index da103db1..00000000 --- a/config/nt/scripts/default_password_scripts.json +++ /dev/null @@ -1,73 +0,0 @@ -{ - "scripts": { - "password_generator.py": "config_password_generator", - "get_password_share.py": "config_get_password_share", - "new_password_share.py": "config_new_password_share" - }, - - "config_password_generator": { - "timeout": 15, - "no_password": true, - "launcher": "python", - "category": "Password", - "path": "./scripts/passwords/password_generator.py", - "description": "This script prints a random ASCII password.", - "command_generate_documentation": "python \"%(dirname)s/../doc/py_doc.py\" \"%(path)s\"" - }, - - "config_get_password_share": { - "timeout": 15, - "launcher": "python", - "category": "Password", - "args": "args_get_password_share", - "path": "./scripts/passwords/get_password_share.py", - "description": "This script decrypt and print a secure password share.", - "command_generate_documentation": "python \"%(dirname)s/../doc/py_doc.py\" \"%(path)s\"" - }, - - "args_get_password_share": { - "token": "arg_token" - }, - - "arg_token": { - "description": "Token to decrypt password.", - "example": "255:MQYipDEHjuyrNrGOaMpRsNr5/WQ/jnRFu2MynY2VabzeiCy2mXzrhHO122/4SpwjTbttUMcdk3NQfD/Y" - }, - - "config_new_password_share": { - "timeout": 15, - "launcher": "python", - "category": "Password", - "content_type": "text/html", - "args": "args_new_password_share", - "path": "./scripts/passwords/new_password_share.py", - "description": "This script share a password securely.", - "command_generate_documentation": "python \"%(dirname)s/../doc/py_doc.py\" \"%(path)s\"" - }, - - "args_new_password_share": { - "password": "arg_password", - "time_in_hours": "arg_time_in_hours", - "maximum_number_of_views": "arg_maximum_number_of_views" - }, - - "arg_password": { - "html_type": "password", - "description": "Password to share." - }, - - "arg_time_in_hours": { - "example": "1.5", - "description": "Maximum time (in hours) to share the password.", - "default_value": 1, - "is_advanced": true - }, - - "arg_maximum_number_of_views": { - "example": "5", - "html_type": "number", - "description": "Maximum number of requests for this password share.", - "default_value": 3, - "is_advanced": true - } -} \ No newline at end of file diff --git a/config/nt/scripts/default_requests_scripts.json b/config/nt/scripts/default_requests_scripts.json deleted file mode 100644 index efef35a0..00000000 --- a/config/nt/scripts/default_requests_scripts.json +++ /dev/null @@ -1,53 +0,0 @@ -{ - "scripts": { - "get_request.py": "config_get_request", - "get_requests.py": "config_get_requests", - "delete_request.py": "config_delete_request" - }, - - "config_get_request": { - "timeout": 15, - "no_password": true, - "launcher": "python", - "access_groups": [1000], - "args": "args_requests", - "category": "User Requests", - "path": "./scripts/request/get_request.py", - "description": "This script prints a user request.", - "command_generate_documentation": "python \"%(dirname)s/../doc/py_doc.py\" \"%(path)s\"" - }, - - "config_get_requests": { - "timeout": 15, - "no_password": true, - "launcher": "python", - "access_groups": [1000], - "content_type": "text/html", - "category": "User Requests", - "path": "./scripts/request/get_requests.py", - "description": "This script prints a HTML table of user requests.", - "command_generate_documentation": "python \"%(dirname)s/../doc/py_doc.py\" \"%(path)s\"" - }, - - "args_requests": { - "ID": "arg_ID" - }, - - "arg_ID": { - "description": "ID of the request.", - "html_type": "number", - "example": "25" - }, - - "config_delete_request": { - "timeout": 15, - "no_password": true, - "launcher": "python", - "access_groups": [1000], - "args": "args_requests", - "category": "User Requests", - "path": "./scripts/request/delete_request.py", - "description": "This script deletes and prints a user request.", - "command_generate_documentation": "python \"%(dirname)s/../doc/py_doc.py\" \"%(path)s\"" - } -} \ No newline at end of file diff --git a/config/nt/scripts/default_rss_scripts.json b/config/nt/scripts/default_rss_scripts.json deleted file mode 100644 index 7054810f..00000000 --- a/config/nt/scripts/default_rss_scripts.json +++ /dev/null @@ -1,64 +0,0 @@ -{ - "scripts": { - "add_news.py": "config_add_news" - }, - - "config_add_news": { - "timeout": 10, - "no_password": true, - "launcher": "python", - "access_groups": [500, 750, 1000], - "category": "RSS Feed", - "args": "args_add_news", - "description": "This script adds a news in the RSS feed", - "command_generate_documentation": "python \"%(dirname)s/../doc/py_doc.py\" \"%(path)s\"" - }, - - "args_add_news": { - "title": "arg_title", - "link": "arg_link", - "categories": "arg_categories", - "--comments": "arg_comments", - "--is-b64": "arg_is_b64", - "description": "arg_description" - }, - - "arg_title": { - "example": "What's new in WebScripts 3.0.0 ?", - "description": "The news title" - }, - - "arg_link": { - "example": "https://webscripts.local/static/WebScripts3.0.0New.html", - "description": "Link to read the full article" - }, - - "arg_categories": { - "list": true, - "example": "WebScripts", - "description": "The news categories" - }, - - "arg_comments": { - "example": "Article written for WebScripts administrators to understand changes in the version 3.0.0", - "description": "A comment for the news." - }, - - "arg_is_b64": { - "html_type": "checkbox", - "description": "Using base64 to upload the file.", - "javascript_section": "javascript_b64", - "is_advanced": true - }, - - "javascript_b64": { - "disabled": true, - "checked": true - }, - - "arg_description": { - "input": true, - "html_type": "file", - "description": "The news content (article or description)." - } -} \ No newline at end of file diff --git a/config/nt/scripts/default_uploads_scripts.json b/config/nt/scripts/default_uploads_scripts.json deleted file mode 100644 index fecb538c..00000000 --- a/config/nt/scripts/default_uploads_scripts.json +++ /dev/null @@ -1,247 +0,0 @@ -{ - "scripts": { - "upload_file.py": "config_upload_file", - "delete_file.py": "config_delete_file", - "download_filename.py": "config_get_file", - "HTML_visible_files.py": "config_get_files", - "HTML_all_files.py": "config_get_all_files", - "HTML_file_history.py": "config_get_history", - "download_all_files.py": "config_get_any_file", - "JSON_visible_files.py": "config_api_get_files", - "JSON_all_files.py": "config_api_get_all_files", - "JSON_file_history.py": "config_api_get_history", - "HTML_uploads_properties.py": "config_web_upload_size", - "JSON_uploads_properties.py": "config_api_upload_size" - }, - - "config_get_file": { - "timeout": 15, - "no_password": true, - "launcher": "python", - "category": "Upload", - "minimum_access": 50, - "args": "args_get_file", - "content_type": "text/html", - "description": "This script returns a download link for a file.", - "command_generate_documentation": "python \"%(dirname)s/../doc/py_doc.py\" \"%(path)s\"" - }, - - "args_get_file": { - "filename": "arg_filename" - }, - - "config_get_files": { - "timeout": 15, - "no_password": true, - "launcher": "python", - "category": "Upload", - "minimum_access": 50, - "content_type": "text/html", - "description": "This script returns a HTML table of uploaded files.", - "command_generate_documentation": "python \"%(dirname)s/../doc/py_doc.py\" \"%(path)s\"" - }, - - "arg_filename": { - "example": "LICENSE.txt", - "description": "The filename of the uploaded file." - }, - - "config_delete_file": { - "timeout": 15, - "no_password": true, - "launcher": "python", - "category": "Upload", - "minimum_access": 50, - "args": "args_get_file", - "description": "This script delete an uploaded file.", - "command_generate_documentation": "python \"%(dirname)s/../doc/py_doc.py\" \"%(path)s\"" - - }, - - "arg_read_permission": { - "example": "255", - "html_type": "number", - "description": "The minimum group ID to read this file.", - "is_advanced": true - }, - - "arg_write_permission": { - "example": "255", - "html_type": "number", - "description": "The minimum group ID to write this file.", - "is_advanced": true - }, - - "arg_delete_permission": { - "example": "255", - "html_type": "number", - "description": "The minimum group ID to delete this file.", - "is_advanced": true - }, - - "arg_hidden": { - "html_type": "checkbox", - "description": "Hide the uploaded file (not visible in the Web Interface).", - "is_advanced": true - }, - - "arg_binary": { - "html_type": "checkbox", - "description": "The uploaded file is binary file (not human readable).", - "is_advanced": true - }, - - "arg_is_b64": { - "html_type": "checkbox", - "description": "Using base64 to upload the file.", - "javascript_section": "javascript_b64", - "is_advanced": true - }, - - "javascript_b64": { - "disabled": true, - "checked": true - }, - - "arg_compression": { - "html_type": "checkbox", - "description": "Do not compress the file (use it for compressed file like ZIP, GZ, BZ2, XZ).", - "is_advanced": true - }, - - "arg_content": { - "input": true, - "html_type": "file", - "description": "Content of the uploaded file." - }, - - "config_get_history": { - "timeout": 15, - "no_password": true, - "launcher": "python", - "category": "Upload", - "args": "args_get_file", - "access_groups": [1000], - "content_type": "text/html", - "description": "This script list all versions for an uploaded file.", - "command_generate_documentation": "python \"%(dirname)s/../doc/py_doc.py\" \"%(path)s\"" - }, - - "config_upload_file": { - "timeout": 300, - "no_password": true, - "launcher": "python", - "category": "Upload", - "minimum_access": 50, - "args": "args_upload_file", - "description": "This script uploads a file on the WebScripts Server.", - "command_generate_documentation": "python \"%(dirname)s/../doc/py_doc.py\" \"%(path)s\"" - }, - - "args_upload_file": { - "name": "arg_filename", - "content": "arg_content", - "--read-permission": "arg_read_permission", - "--write-permission": "arg_write_permission", - "--delete-permission": "arg_delete_permission", - "--hidden": "arg_hidden", - "--binary": "arg_binary", - "--no-compression": "arg_compression", - "--is-b64": "arg_is_b64" - }, - - "config_api_get_files": { - "timeout": 15, - "no_password": true, - "launcher": "python", - "category": "Upload", - "minimum_access": 50, - "content_type": "text/json", - "description": "This script returns a JSON object of uploaded files.", - "command_generate_documentation": "python \"%(dirname)s/../doc/py_doc.py\" \"%(path)s\"" - }, - - "config_get_all_files": { - "timeout": 15, - "no_password": true, - "launcher": "python", - "category": "Upload", - "access_groups": [1000], - "content_type": "text/html", - "description": "This script returns a HTML table of all uploaded files.", - "command_generate_documentation": "python \"%(dirname)s/../doc/py_doc.py\" \"%(path)s\"" - }, - - "config_api_get_history": { - "timeout": 15, - "no_password": true, - "launcher": "python", - "category": "Upload", - "access_groups": [1000], - "args": "args_get_file", - "content_type": "text/json", - "description": "This script list all groups to get IDs, access level and names.", - "command_generate_documentation": "python \"%(dirname)s/../doc/py_doc.py\" \"%(path)s\"" - }, - - "config_api_get_all_files": { - "timeout": 15, - "no_password": true, - "launcher": "python", - "category": "Upload", - "access_groups": [1000], - "content_type": "text/json", - "description": "This script list all versions for an uploaded file.", - "command_generate_documentation": "python \"%(dirname)s/../doc/py_doc.py\" \"%(path)s\"" - }, - - "config_get_any_file": { - "timeout": 15, - "no_password": true, - "launcher": "python", - "category": "Upload", - "access_groups": [1000], - "args": "args_get_any_file", - "content_type": "text/html", - "description": "This script returns a download link for any file (old version and without permission).", - "command_generate_documentation": "python \"%(dirname)s/../doc/py_doc.py\" \"%(path)s\"" - }, - - "args_get_any_file": { - "type": "arg_identifier_type", - "identifier": "arg_identifier" - }, - - "arg_identifier_type": { - "description": "Type o the identifier (ID or name).", - "example": "name", - "predefined_values": ["name", "ID"] - }, - - "arg_identifier": { - "description": "The name of the file or the ID of the version.", - "example": "LICENSE.txt" - }, - - "config_web_upload_size": { - "timeout": 15, - "no_password": true, - "launcher": "python", - "category": "Upload", - "access_groups": [1000], - "content_type": "text/csv", - "description": "This script prints a HTML table of uploaded file metadata.", - "command_generate_documentation": "python \"%(dirname)s/../doc/py_doc.py\" \"%(path)s\"" - }, - - "config_api_upload_size": { - "timeout": 15, - "no_password": true, - "launcher": "python", - "category": "Upload", - "access_groups": [1000], - "content_type": "text/json", - "description": "This script returns a JSON object of uploaded file metadata.", - "command_generate_documentation": "python \"%(dirname)s/../doc/py_doc.py\" \"%(path)s\"" - } -} \ No newline at end of file diff --git a/config/nt/server.ini b/config/nt/server.ini deleted file mode 100644 index aad9499a..00000000 --- a/config/nt/server.ini +++ /dev/null @@ -1,167 +0,0 @@ -[server] -interface=127.0.0.1 # required value -port=8000 # required value - -debug=false # Export the full server configuration and get error messages on HTTP errors pages [NEVER true in production] -security=true # Add security HTTP headers -force_file_permissions=true # Don't load file if permissions are not secure - -accept_unknow_user=false # Don't force a user to re-authenticate -accept_unauthenticated_user=false # Don't force authentication for new user -active_auth=true # Active auth page -auth_script=auth.py # Change it to use a custom authentication script -auth_failures_to_blacklist=3 # Number of authentication failures to blacklist an IP address or user -blacklist_time=30 # Blacklist time in seconds -admin_groups=1000 # Integer list to defines Adminitrators groups -exclude_auth_paths=/static/,/js/ # Start of paths where the unauthenticated user gets access -exclude_auth_pages=/api/,/auth/,/web/auth/ # Specific page where the unauthenticated user has access -session_max_time=3600 # Maximum time in seconds of sessions (recommended value: 3600) -csrf_max_time=300 # Maximum time in seconds of csrf tokens (recommended value: 300) - -urls_section=urls # Defined the URL routing section - -scripts_path=./scripts/rss,./scripts/account,./scripts/passwords,./scripts/uploads # Add scripts from location -json_scripts_config=./config/scripts/*.json,./config/nt/scripts/*.json # Add server configuration (syntax: json) -ini_scripts_config=./config/scripts/*.ini,./config/nt/scripts/*.ini # Add server configuration (syntax: cfg, ini) -documentations_path=./doc/*.html # Add path to search documentation scripts -# modules # Add custom modules (names) to the server -# modules_path # Add directory to import custom modules -modules=error_pages,share,cgi,rss,JsonRpc,notification -modules_path=./modules -js_path=./static/js/*.js # Add glob syntax files to get javascript files -statics_path=./static/html/*.html,./static/css/*.css,./static/images/*.png,./static/images/*.jpg,./static/pdf/*.pdf # Add glob syntax files to get static files - -log_level=DEBUG # Set your custom log level {"DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"} -log_filename=./logs/root.logs # Write your custom logs in this filename -log_format=%(asctime)s %(levelname)s %(message)s (%(funcName)s -> %(filename)s:%(lineno)d) # Format for your custom logs (https://docs.python.org/3/library/logging.html#id2) -log_date_format=%d/%m/%Y %H:%M:%S # Format date for your custom logs (https://docs.python.org/3/library/time.html#time.strftime) -log_encoding=utf-8 # Encoding for your custom log file - -smtp_server # SMTP configuration is used to send notifications, the server name or the IP address of the SMTP server -smtp_starttls=false # Using starttls to secure the connection -smtp_password # Password for email account (username is the notification_address configuration), if password is None the client send email without authentication -smtp_port=25 # SMTP port -smtp_ssl=false # Using SSL (not starttls) to secure the connection -admin_adresses=admin1@webscripts.local,admin2@webscripts.local # Administrators email addresses to receive the notification -notification_address=notification@webscripts.local # Notification address to send the notification (the sender email address) - -webproxy_number=0 # Number of web proxies in front of the WebScripts server (this is the security configuration to block IP address spoofing: proxies add a header to send the client's IP address and proxies use different headers, there is no way to know which header is the right IP but it is possible to check the number of IPs in the headers). This configuration impact the bruteforce protection and the IP filtering authentication. Without this configuration the IP spoofing protection will be disabled (the bruteforce protection and IP filtering in authentication will be ineffective). - -base_url=/ # Base URL for the WebScripts server, if last character is not "/" it will be added by WebScripts, you can set the base URL configuration to use your server on non root URL, this is useful when you use multiples Web server on one host you could have the WebScripts web interface on /test/web/ with the base_url configuration set to "/test/" or "/test" and another Web server on the "/". - -data_dir=data # Configure data directory -cgi_path=cgi-bin # CGI path to find scripts - -[urls] -/authentication/=/web/auth/ # Routing the URL -> /authentication/ 'redirect' to /web/auth/ -/cgi-bin/=/cgi/bin/ # Routing the URL -> /cgi-bin/ 'redirect' to /cgi/bin/ -/cgi/=/cgi/bin/ # Routing the URL -> /cgi/ 'redirect' to /cgi/bin/ -/bin/=/cgi/bin/ # Routing the URL -> /bin/ 'redirect' to /cgi/bin/ - -[scripts] -auth.py=config_auth # Define the configuration section ("config_auth") for script named "auth.py" -show_license.py=config_show_licence # Same as previous for "show_license.py" with "config_show_licence" -change_my_password.py=config_change_my_password # Same as previous for "change_my_password.py" with "config_change_my_password" -test_config.py=config_test_config # Little script to test arguments and script configurations - -[config_auth] -launcher=python # Define the launcher for this script (if script is executable this line is not necessary) -path=./scripts/account/auth.py # Only necessary if the location of the script is not in "scripts_path" -documentation_file=./doc/auth.html # Only needed if the location of the documentation does not match the paths defined in "documentations_path" -content_type=text/plain # Define the script output content-type (HTTP headers/javascript interpretation) -documentation_content_type=text/html # Define the documentation content-type -minimum_access=0 # If a user's group is greater than "minimum_access", the user can use this script -access_groups=0,1 # If a user's group is in "access_groups", the user can use this script -access_users=0,1,2 # If the user ID is in "access_users", the user can use this script -args=auth_args # The arguments are defined in section named "auth_args" -description=This script authenticates users. # Short description to help users -category=My Account # Add a link on the index page in the "My Account" section -timeout=15 # Timeout for process execution (in seconds) -command_generate_documentation=python "%(dirname)s/../doc/py_doc.py" "%(path)s" # Command line to generate the documentation file - -# Script "auth.py" is in "./scripts/account" and this path is defined in "scripts_path" so is not necessary to add the "path" in configuration section -# Documentation for "auth.py" is "./doc/auth.html" and this path match with "./doc/*.html" (defined in "documentations_path") so is not necessary to add "documentation_file" in configuration section -# The default "Content-Type" is "text/plain" so is not necessary to add "content_type" in configuration section -# The default "Content-Type" for documentation is "text/html" so is not necessary to add "documentation_content_type" in configuration section -# You can add access to a script with: -# - minimum_access: check all user groups, if a group is greater than "minimum_access", the user can use this script -# - access_groups: check all user groups, if a group is in "access_groups", the user can use this script -# - access_users: if the user ID is in "access_users", the user can use this script -# - if script configuration don't have "minimum_access", "access_groups" and "access_users" anyone can use the script -# The "args" configuration is not necessary if you have no arguments for this script -# "description" is recommended but not required -# If this script does not have a "category", this script will not be visible in the index page (WEB interface only) -# Without "timeout", the script can run indefinitely -# You can run a command line to generate the documentation before printing it, with the "command_generate_documentation" configuration - -[auth_args] ---username=arg_username # Add a configuration section ("arg_username") for the argument named "--username" ---password=arg_password # Add a configuration section ("arg_password") for the argument named "--password" - -# If the name of the argument starts with "-", it will be added in the command line otherwise only the value is added - -[arg_username] -html_type=text # Define the HTML input type for this argument -description=Your username (to log in) # Short description to help users -default_value # Add default value -predefined_values # To build a list box ( in HTML) with a list of values -example=user # Add example (placeholder in HTML) -list=false # Only one username, if true the user can add usernames (as much as the user wants) -input=false # To send the argument in STDIN (interactive mode) - -# The default "html_type" is "text" and is therefore not required for this argument -# "description" is recommended but not required -# You can build a list box when an argument value must be in a list of predifined values (html_type will have no consequences) -# "example" is recommended but not required -# When your argument can have multiple values (a list of values) in the same run, list must be true -# When your script has an interactive mode, you can use the "input" configuration to send the argument to STDIN - -[arg_password] -html_type=password # Define the HTML input type for this argument -description=Your password (to log in) # Short description to help users -example=password # Add example (placeholder in HTML) - -[config_change_my_password] -configuration_file=./config/files/change_my_password.json # Define script configuration in a specific file - -[config_test_config] -configuration_file=./config/files/test_config.json - -[config_show_licence] -timeout=15 # Timeout for process execution (in seconds) -launcher=python3 # Define the launcher for this script (if script is executable this line is not necessary) -category=License # Add a link on the index page in the "License" section -no_password=false # Log the execution command line (must be false if a password is in arguments) -args=args_show_license # The arguments are defined in section named "args_show_license" -path=./scripts/py/show_license.py # Only necessary if the location of the script is not in "scripts_path" -description=This file display the license and copyright of WebScripts. # Short description to help users -command_generate_documentation=python3 ./scripts/doc/py_doc.py ./scripts/py/show_license.py # Command line to generate the documentation file - -[args_show_license] -license=arg_license # Add a configuration section ("arg_license") for the argument named "license" -copyright=arg_copyright # Add a configuration section ("arg_copyright") for the argument named "copyright" -codeheader=arg_codeheader # Add a configuration section ("arg_codeheader") for the argument named "codeheader" - -[arg_license] -html_type=checkbox # Define the HTML input type for this argument -description=Display the header of the WebScripts code # Short description to help users - -[arg_copyright] -html_type=checkbox # Define the HTML input type for this argument -description=View full license # Short description to help users - -[arg_codeheader] -html_type=checkbox # Define the HTML input type for this argument -description=See copyright # Short description to help users - -################ -# ! CAUTION ! -# Priority level for server configuration is: -# 1. Command line arguments -# 2. Configuration files -# -# Priority level for configuration files is: -# 1. your customs configuration JSON files -# 2. your customs configuration INI files -# 3. default JSON configuration file (./config/server.json) -# 4. default INI configuration file (this file: ./config/server.ini) \ No newline at end of file diff --git a/config/server.json b/config/server.json deleted file mode 100644 index 45268fea..00000000 --- a/config/server.json +++ /dev/null @@ -1,150 +0,0 @@ -{ - "server": { - "interface": "127.0.0.1", - "port": 8000, - "debug": false, - "security": true, - "force_file_permissions": false, - "accept_unknow_user": false, - "accept_unauthenticated_user": false, - "active_auth": true, - "auth_script": "auth.py", - "auth_failures_to_blacklist": 3, - "blacklist_time": 30, - "admin_groups": [ - 1000 - ], - "exclude_auth_paths": [ - "/static/", - "/js/" - ], - "exclude_auth_pages": [ - "/api/", - "/auth/", - "/web/auth/" - ], - "session_max_time": 3600, - "csrf_max_time": 300, - "urls_section": "urls", - "scripts_path": [ - "./scripts/rss", - "./scripts/account", - "./scripts/passwords", - "./scripts/uploads" - ], - "json_scripts_config": [ - "./config/scripts/*.json" - ], - "ini_scripts_config": [ - "./config/scripts/*.ini" - ], - "documentations_path": [ - "./doc/*.html" - ], - "modules": [ - "error_pages", - "share", - "cgi", - "rss", - "JsonRpc", - "notification" - ], - "modules_path": ["./modules/"], - "js_path": [ - "./static/js/*.js" - ], - "statics_path": [ - "./static/html/*.html", - "./static/css/*.css", - "./static/images/*.png", - "./static/images/*.jpg", - "./static/pdf/*.pdf" - ], - "log_level": "0", - "log_filename": "./logs/root.logs", - "log_format": "%(asctime)s %(levelname)s %(message)s (%(funcName)s -> %(filename)s:%(lineno)d)", - "log_date_format": "%d/%m/%Y %H:%M:%S", - "log_encoding": "utf-8", - "smtp_server": null, - "smtp_starttls": false, - "smtp_password": null, - "smtp_port": 25, - "smtp_ssl": false, - "admin_adresses": [ - "admin1@webscripts.local", - "admin2@webscripts.local" - ], - "notification_address": "notification@webscripts.local", - "webproxy_number": 0, - "base_url": "", - "data_dir": "data", - "cgi_path": [ - "cgi-bin" - ] - }, - "urls": { - "/authentication/": "/web/auth/", - "/cgi-bin/": "/cgi/bin/", - "/bin/": "/cgi/bin/", - "/cgi/": "/cgi/bin/" - }, - "scripts": { - "auth.py": "config_auth", - "show_license.py": "config_show_licence", - "change_my_password.py": "config_change_my_password" - }, - "config_auth": { - "timeout": 15, - "args": "auth_args", - "launcher": "/usr/bin/python3", - "category": "My Account", - "description": "This script authenticates users.", - "path": "./scripts/account/auth.py" - }, - "auth_args": { - "--username": "arg_username", - "--password": "arg_password" - }, - "arg_password": { - "example": "password", - "html_type": "password", - "description": "Your password (to log in)" - }, - "arg_username": { - "example": "user", - "description": "Your username (to log in)" - }, - "config_change_my_password": { - "configuration_file": "./config/files/change_my_password.json" - }, - "config_test_config": { - "configuration_file": "./config/files/test_config.json" - }, - "config_show_licence": { - "timeout": 15, - "no_password": true, - "launcher": "/usr/bin/python3", - "category": "License", - "args": "args_show_license", - "path": "./scripts/py/show_license.py", - "description": "This file display the license and copyright of WebScripts.", - "command_generate_documentation": "python3 \"%(dirname)s/../doc/py_doc.py\" \"%(path)s\"" - }, - "args_show_license": { - "license": "arg_license", - "copyright": "arg_copyright", - "codeheader": "arg_codeheader" - }, - "arg_codeheader": { - "html_type": "checkbox", - "description": "Display the header of the WebScripts code" - }, - "arg_license": { - "html_type": "checkbox", - "description": "View full license" - }, - "arg_copyright": { - "html_type": "checkbox", - "description": "See copyright" - } -} diff --git a/data/groups.csv b/data/groups.csv deleted file mode 100644 index 424a2fa0..00000000 --- a/data/groups.csv +++ /dev/null @@ -1,7 +0,0 @@ -"ID","name" -"0","Not Authenticated" -"1","Unknow" -"50","User" -"500","Developers" -"750","Maintainers" -"1000","Administrators" diff --git a/data/id b/data/id deleted file mode 100644 index c2270834..00000000 --- a/data/id +++ /dev/null @@ -1 +0,0 @@ -0 \ No newline at end of file diff --git a/data/passwords.csv b/data/passwords.csv deleted file mode 100644 index 0b92cdc3..00000000 --- a/data/passwords.csv +++ /dev/null @@ -1 +0,0 @@ -0,,0,,0,0 diff --git a/data/requests.csv b/data/requests.csv deleted file mode 100644 index 04b4897f..00000000 --- a/data/requests.csv +++ /dev/null @@ -1 +0,0 @@ -ID,Time,UserName,ErrorCode,Page,UserAgent,Subject,Reason,Name diff --git a/data/rss.csv b/data/rss.csv deleted file mode 100644 index ea9efea0..00000000 --- a/data/rss.csv +++ /dev/null @@ -1 +0,0 @@ -guid,author,title,description,link,categories,pubDate,comments diff --git a/data/uploads.csv b/data/uploads.csv deleted file mode 100644 index 610dd222..00000000 --- a/data/uploads.csv +++ /dev/null @@ -1 +0,0 @@ -0,LICENSE.txt,0,5000,5000,visible,exist,text,1627229476.5933902,Admin,0 diff --git a/data/uploads/LICENSE_210725_181116.txt b/data/uploads/LICENSE_210725_181116.txt deleted file mode 100644 index 3877ae0a..00000000 --- a/data/uploads/LICENSE_210725_181116.txt +++ /dev/null @@ -1,674 +0,0 @@ - GNU GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The GNU General Public License is a free, copyleft license for -software and other kinds of works. - - The licenses for most software and other practical works are designed -to take away your freedom to share and change the works. By contrast, -the GNU General Public License is intended to guarantee your freedom to -share and change all versions of a program--to make sure it remains free -software for all its users. We, the Free Software Foundation, use the -GNU General Public License for most of our software; it applies also to -any other work released this way by its authors. You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -them if you wish), that you receive source code or can get it if you -want it, that you can change the software or use pieces of it in new -free programs, and that you know you can do these things. - - To protect your rights, we need to prevent others from denying you -these rights or asking you to surrender the rights. Therefore, you have -certain responsibilities if you distribute copies of the software, or if -you modify it: responsibilities to respect the freedom of others. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must pass on to the recipients the same -freedoms that you received. You must make sure that they, too, receive -or can get the source code. And you must show them these terms so they -know their rights. - - Developers that use the GNU GPL protect your rights with two steps: -(1) assert copyright on the software, and (2) offer you this License -giving you legal permission to copy, distribute and/or modify it. - - For the developers' and authors' protection, the GPL clearly explains -that there is no warranty for this free software. For both users' and -authors' sake, the GPL requires that modified versions be marked as -changed, so that their problems will not be attributed erroneously to -authors of previous versions. - - Some devices are designed to deny users access to install or run -modified versions of the software inside them, although the manufacturer -can do so. This is fundamentally incompatible with the aim of -protecting users' freedom to change the software. The systematic -pattern of such abuse occurs in the area of products for individuals to -use, which is precisely where it is most unacceptable. Therefore, we -have designed this version of the GPL to prohibit the practice for those -products. If such problems arise substantially in other domains, we -stand ready to extend this provision to those domains in future versions -of the GPL, as needed to protect the freedom of users. - - Finally, every program is threatened constantly by software patents. -States should not allow patents to restrict development and use of -software on general-purpose computers, but in those that do, we wish to -avoid the special danger that patents applied to a free program could -make it effectively proprietary. To prevent this, the GPL assures that -patents cannot be used to render the program non-free. - - The precise terms and conditions for copying, distribution and -modification follow. - - TERMS AND CONDITIONS - - 0. Definitions. - - "This License" refers to version 3 of the GNU General Public License. - - "Copyright" also means copyright-like laws that apply to other kinds of -works, such as semiconductor masks. - - "The Program" refers to any copyrightable work licensed under this -License. Each licensee is addressed as "you". "Licensees" and -"recipients" may be individuals or organizations. - - To "modify" a work means to copy from or adapt all or part of the work -in a fashion requiring copyright permission, other than the making of an -exact copy. The resulting work is called a "modified version" of the -earlier work or a work "based on" the earlier work. - - A "covered work" means either the unmodified Program or a work based -on the Program. - - To "propagate" a work means to do anything with it that, without -permission, would make you directly or secondarily liable for -infringement under applicable copyright law, except executing it on a -computer or modifying a private copy. Propagation includes copying, -distribution (with or without modification), making available to the -public, and in some countries other activities as well. - - To "convey" a work means any kind of propagation that enables other -parties to make or receive copies. Mere interaction with a user through -a computer network, with no transfer of a copy, is not conveying. - - An interactive user interface displays "Appropriate Legal Notices" -to the extent that it includes a convenient and prominently visible -feature that (1) displays an appropriate copyright notice, and (2) -tells the user that there is no warranty for the work (except to the -extent that warranties are provided), that licensees may convey the -work under this License, and how to view a copy of this License. If -the interface presents a list of user commands or options, such as a -menu, a prominent item in the list meets this criterion. - - 1. Source Code. - - The "source code" for a work means the preferred form of the work -for making modifications to it. "Object code" means any non-source -form of a work. - - A "Standard Interface" means an interface that either is an official -standard defined by a recognized standards body, or, in the case of -interfaces specified for a particular programming language, one that -is widely used among developers working in that language. - - The "System Libraries" of an executable work include anything, other -than the work as a whole, that (a) is included in the normal form of -packaging a Major Component, but which is not part of that Major -Component, and (b) serves only to enable use of the work with that -Major Component, or to implement a Standard Interface for which an -implementation is available to the public in source code form. A -"Major Component", in this context, means a major essential component -(kernel, window system, and so on) of the specific operating system -(if any) on which the executable work runs, or a compiler used to -produce the work, or an object code interpreter used to run it. - - The "Corresponding Source" for a work in object code form means all -the source code needed to generate, install, and (for an executable -work) run the object code and to modify the work, including scripts to -control those activities. However, it does not include the work's -System Libraries, or general-purpose tools or generally available free -programs which are used unmodified in performing those activities but -which are not part of the work. For example, Corresponding Source -includes interface definition files associated with source files for -the work, and the source code for shared libraries and dynamically -linked subprograms that the work is specifically designed to require, -such as by intimate data communication or control flow between those -subprograms and other parts of the work. - - The Corresponding Source need not include anything that users -can regenerate automatically from other parts of the Corresponding -Source. - - The Corresponding Source for a work in source code form is that -same work. - - 2. Basic Permissions. - - All rights granted under this License are granted for the term of -copyright on the Program, and are irrevocable provided the stated -conditions are met. This License explicitly affirms your unlimited -permission to run the unmodified Program. The output from running a -covered work is covered by this License only if the output, given its -content, constitutes a covered work. This License acknowledges your -rights of fair use or other equivalent, as provided by copyright law. - - You may make, run and propagate covered works that you do not -convey, without conditions so long as your license otherwise remains -in force. You may convey covered works to others for the sole purpose -of having them make modifications exclusively for you, or provide you -with facilities for running those works, provided that you comply with -the terms of this License in conveying all material for which you do -not control copyright. Those thus making or running the covered works -for you must do so exclusively on your behalf, under your direction -and control, on terms that prohibit them from making any copies of -your copyrighted material outside their relationship with you. - - Conveying under any other circumstances is permitted solely under -the conditions stated below. Sublicensing is not allowed; section 10 -makes it unnecessary. - - 3. Protecting Users' Legal Rights From Anti-Circumvention Law. - - No covered work shall be deemed part of an effective technological -measure under any applicable law fulfilling obligations under article -11 of the WIPO copyright treaty adopted on 20 December 1996, or -similar laws prohibiting or restricting circumvention of such -measures. - - When you convey a covered work, you waive any legal power to forbid -circumvention of technological measures to the extent such circumvention -is effected by exercising rights under this License with respect to -the covered work, and you disclaim any intention to limit operation or -modification of the work as a means of enforcing, against the work's -users, your or third parties' legal rights to forbid circumvention of -technological measures. - - 4. Conveying Verbatim Copies. - - You may convey verbatim copies of the Program's source code as you -receive it, in any medium, provided that you conspicuously and -appropriately publish on each copy an appropriate copyright notice; -keep intact all notices stating that this License and any -non-permissive terms added in accord with section 7 apply to the code; -keep intact all notices of the absence of any warranty; and give all -recipients a copy of this License along with the Program. - - You may charge any price or no price for each copy that you convey, -and you may offer support or warranty protection for a fee. - - 5. Conveying Modified Source Versions. - - You may convey a work based on the Program, or the modifications to -produce it from the Program, in the form of source code under the -terms of section 4, provided that you also meet all of these conditions: - - a) The work must carry prominent notices stating that you modified - it, and giving a relevant date. - - b) The work must carry prominent notices stating that it is - released under this License and any conditions added under section - 7. This requirement modifies the requirement in section 4 to - "keep intact all notices". - - c) You must license the entire work, as a whole, under this - License to anyone who comes into possession of a copy. This - License will therefore apply, along with any applicable section 7 - additional terms, to the whole of the work, and all its parts, - regardless of how they are packaged. This License gives no - permission to license the work in any other way, but it does not - invalidate such permission if you have separately received it. - - d) If the work has interactive user interfaces, each must display - Appropriate Legal Notices; however, if the Program has interactive - interfaces that do not display Appropriate Legal Notices, your - work need not make them do so. - - A compilation of a covered work with other separate and independent -works, which are not by their nature extensions of the covered work, -and which are not combined with it such as to form a larger program, -in or on a volume of a storage or distribution medium, is called an -"aggregate" if the compilation and its resulting copyright are not -used to limit the access or legal rights of the compilation's users -beyond what the individual works permit. Inclusion of a covered work -in an aggregate does not cause this License to apply to the other -parts of the aggregate. - - 6. Conveying Non-Source Forms. - - You may convey a covered work in object code form under the terms -of sections 4 and 5, provided that you also convey the -machine-readable Corresponding Source under the terms of this License, -in one of these ways: - - a) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by the - Corresponding Source fixed on a durable physical medium - customarily used for software interchange. - - b) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by a - written offer, valid for at least three years and valid for as - long as you offer spare parts or customer support for that product - model, to give anyone who possesses the object code either (1) a - copy of the Corresponding Source for all the software in the - product that is covered by this License, on a durable physical - medium customarily used for software interchange, for a price no - more than your reasonable cost of physically performing this - conveying of source, or (2) access to copy the - Corresponding Source from a network server at no charge. - - c) Convey individual copies of the object code with a copy of the - written offer to provide the Corresponding Source. This - alternative is allowed only occasionally and noncommercially, and - only if you received the object code with such an offer, in accord - with subsection 6b. - - d) Convey the object code by offering access from a designated - place (gratis or for a charge), and offer equivalent access to the - Corresponding Source in the same way through the same place at no - further charge. You need not require recipients to copy the - Corresponding Source along with the object code. If the place to - copy the object code is a network server, the Corresponding Source - may be on a different server (operated by you or a third party) - that supports equivalent copying facilities, provided you maintain - clear directions next to the object code saying where to find the - Corresponding Source. Regardless of what server hosts the - Corresponding Source, you remain obligated to ensure that it is - available for as long as needed to satisfy these requirements. - - e) Convey the object code using peer-to-peer transmission, provided - you inform other peers where the object code and Corresponding - Source of the work are being offered to the general public at no - charge under subsection 6d. - - A separable portion of the object code, whose source code is excluded -from the Corresponding Source as a System Library, need not be -included in conveying the object code work. - - A "User Product" is either (1) a "consumer product", which means any -tangible personal property which is normally used for personal, family, -or household purposes, or (2) anything designed or sold for incorporation -into a dwelling. In determining whether a product is a consumer product, -doubtful cases shall be resolved in favor of coverage. For a particular -product received by a particular user, "normally used" refers to a -typical or common use of that class of product, regardless of the status -of the particular user or of the way in which the particular user -actually uses, or expects or is expected to use, the product. A product -is a consumer product regardless of whether the product has substantial -commercial, industrial or non-consumer uses, unless such uses represent -the only significant mode of use of the product. - - "Installation Information" for a User Product means any methods, -procedures, authorization keys, or other information required to install -and execute modified versions of a covered work in that User Product from -a modified version of its Corresponding Source. The information must -suffice to ensure that the continued functioning of the modified object -code is in no case prevented or interfered with solely because -modification has been made. - - If you convey an object code work under this section in, or with, or -specifically for use in, a User Product, and the conveying occurs as -part of a transaction in which the right of possession and use of the -User Product is transferred to the recipient in perpetuity or for a -fixed term (regardless of how the transaction is characterized), the -Corresponding Source conveyed under this section must be accompanied -by the Installation Information. But this requirement does not apply -if neither you nor any third party retains the ability to install -modified object code on the User Product (for example, the work has -been installed in ROM). - - The requirement to provide Installation Information does not include a -requirement to continue to provide support service, warranty, or updates -for a work that has been modified or installed by the recipient, or for -the User Product in which it has been modified or installed. Access to a -network may be denied when the modification itself materially and -adversely affects the operation of the network or violates the rules and -protocols for communication across the network. - - Corresponding Source conveyed, and Installation Information provided, -in accord with this section must be in a format that is publicly -documented (and with an implementation available to the public in -source code form), and must require no special password or key for -unpacking, reading or copying. - - 7. Additional Terms. - - "Additional permissions" are terms that supplement the terms of this -License by making exceptions from one or more of its conditions. -Additional permissions that are applicable to the entire Program shall -be treated as though they were included in this License, to the extent -that they are valid under applicable law. If additional permissions -apply only to part of the Program, that part may be used separately -under those permissions, but the entire Program remains governed by -this License without regard to the additional permissions. - - When you convey a copy of a covered work, you may at your option -remove any additional permissions from that copy, or from any part of -it. (Additional permissions may be written to require their own -removal in certain cases when you modify the work.) You may place -additional permissions on material, added by you to a covered work, -for which you have or can give appropriate copyright permission. - - Notwithstanding any other provision of this License, for material you -add to a covered work, you may (if authorized by the copyright holders of -that material) supplement the terms of this License with terms: - - a) Disclaiming warranty or limiting liability differently from the - terms of sections 15 and 16 of this License; or - - b) Requiring preservation of specified reasonable legal notices or - author attributions in that material or in the Appropriate Legal - Notices displayed by works containing it; or - - c) Prohibiting misrepresentation of the origin of that material, or - requiring that modified versions of such material be marked in - reasonable ways as different from the original version; or - - d) Limiting the use for publicity purposes of names of licensors or - authors of the material; or - - e) Declining to grant rights under trademark law for use of some - trade names, trademarks, or service marks; or - - f) Requiring indemnification of licensors and authors of that - material by anyone who conveys the material (or modified versions of - it) with contractual assumptions of liability to the recipient, for - any liability that these contractual assumptions directly impose on - those licensors and authors. - - All other non-permissive additional terms are considered "further -restrictions" within the meaning of section 10. If the Program as you -received it, or any part of it, contains a notice stating that it is -governed by this License along with a term that is a further -restriction, you may remove that term. If a license document contains -a further restriction but permits relicensing or conveying under this -License, you may add to a covered work material governed by the terms -of that license document, provided that the further restriction does -not survive such relicensing or conveying. - - If you add terms to a covered work in accord with this section, you -must place, in the relevant source files, a statement of the -additional terms that apply to those files, or a notice indicating -where to find the applicable terms. - - Additional terms, permissive or non-permissive, may be stated in the -form of a separately written license, or stated as exceptions; -the above requirements apply either way. - - 8. Termination. - - You may not propagate or modify a covered work except as expressly -provided under this License. Any attempt otherwise to propagate or -modify it is void, and will automatically terminate your rights under -this License (including any patent licenses granted under the third -paragraph of section 11). - - However, if you cease all violation of this License, then your -license from a particular copyright holder is reinstated (a) -provisionally, unless and until the copyright holder explicitly and -finally terminates your license, and (b) permanently, if the copyright -holder fails to notify you of the violation by some reasonable means -prior to 60 days after the cessation. - - Moreover, your license from a particular copyright holder is -reinstated permanently if the copyright holder notifies you of the -violation by some reasonable means, this is the first time you have -received notice of violation of this License (for any work) from that -copyright holder, and you cure the violation prior to 30 days after -your receipt of the notice. - - Termination of your rights under this section does not terminate the -licenses of parties who have received copies or rights from you under -this License. If your rights have been terminated and not permanently -reinstated, you do not qualify to receive new licenses for the same -material under section 10. - - 9. Acceptance Not Required for Having Copies. - - You are not required to accept this License in order to receive or -run a copy of the Program. Ancillary propagation of a covered work -occurring solely as a consequence of using peer-to-peer transmission -to receive a copy likewise does not require acceptance. However, -nothing other than this License grants you permission to propagate or -modify any covered work. These actions infringe copyright if you do -not accept this License. Therefore, by modifying or propagating a -covered work, you indicate your acceptance of this License to do so. - - 10. Automatic Licensing of Downstream Recipients. - - Each time you convey a covered work, the recipient automatically -receives a license from the original licensors, to run, modify and -propagate that work, subject to this License. You are not responsible -for enforcing compliance by third parties with this License. - - An "entity transaction" is a transaction transferring control of an -organization, or substantially all assets of one, or subdividing an -organization, or merging organizations. If propagation of a covered -work results from an entity transaction, each party to that -transaction who receives a copy of the work also receives whatever -licenses to the work the party's predecessor in interest had or could -give under the previous paragraph, plus a right to possession of the -Corresponding Source of the work from the predecessor in interest, if -the predecessor has it or can get it with reasonable efforts. - - You may not impose any further restrictions on the exercise of the -rights granted or affirmed under this License. For example, you may -not impose a license fee, royalty, or other charge for exercise of -rights granted under this License, and you may not initiate litigation -(including a cross-claim or counterclaim in a lawsuit) alleging that -any patent claim is infringed by making, using, selling, offering for -sale, or importing the Program or any portion of it. - - 11. Patents. - - A "contributor" is a copyright holder who authorizes use under this -License of the Program or a work on which the Program is based. The -work thus licensed is called the contributor's "contributor version". - - A contributor's "essential patent claims" are all patent claims -owned or controlled by the contributor, whether already acquired or -hereafter acquired, that would be infringed by some manner, permitted -by this License, of making, using, or selling its contributor version, -but do not include claims that would be infringed only as a -consequence of further modification of the contributor version. For -purposes of this definition, "control" includes the right to grant -patent sublicenses in a manner consistent with the requirements of -this License. - - Each contributor grants you a non-exclusive, worldwide, royalty-free -patent license under the contributor's essential patent claims, to -make, use, sell, offer for sale, import and otherwise run, modify and -propagate the contents of its contributor version. - - In the following three paragraphs, a "patent license" is any express -agreement or commitment, however denominated, not to enforce a patent -(such as an express permission to practice a patent or covenant not to -sue for patent infringement). To "grant" such a patent license to a -party means to make such an agreement or commitment not to enforce a -patent against the party. - - If you convey a covered work, knowingly relying on a patent license, -and the Corresponding Source of the work is not available for anyone -to copy, free of charge and under the terms of this License, through a -publicly available network server or other readily accessible means, -then you must either (1) cause the Corresponding Source to be so -available, or (2) arrange to deprive yourself of the benefit of the -patent license for this particular work, or (3) arrange, in a manner -consistent with the requirements of this License, to extend the patent -license to downstream recipients. "Knowingly relying" means you have -actual knowledge that, but for the patent license, your conveying the -covered work in a country, or your recipient's use of the covered work -in a country, would infringe one or more identifiable patents in that -country that you have reason to believe are valid. - - If, pursuant to or in connection with a single transaction or -arrangement, you convey, or propagate by procuring conveyance of, a -covered work, and grant a patent license to some of the parties -receiving the covered work authorizing them to use, propagate, modify -or convey a specific copy of the covered work, then the patent license -you grant is automatically extended to all recipients of the covered -work and works based on it. - - A patent license is "discriminatory" if it does not include within -the scope of its coverage, prohibits the exercise of, or is -conditioned on the non-exercise of one or more of the rights that are -specifically granted under this License. You may not convey a covered -work if you are a party to an arrangement with a third party that is -in the business of distributing software, under which you make payment -to the third party based on the extent of your activity of conveying -the work, and under which the third party grants, to any of the -parties who would receive the covered work from you, a discriminatory -patent license (a) in connection with copies of the covered work -conveyed by you (or copies made from those copies), or (b) primarily -for and in connection with specific products or compilations that -contain the covered work, unless you entered into that arrangement, -or that patent license was granted, prior to 28 March 2007. - - Nothing in this License shall be construed as excluding or limiting -any implied license or other defenses to infringement that may -otherwise be available to you under applicable patent law. - - 12. No Surrender of Others' Freedom. - - If conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot convey a -covered work so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you may -not convey it at all. For example, if you agree to terms that obligate you -to collect a royalty for further conveying from those to whom you convey -the Program, the only way you could satisfy both those terms and this -License would be to refrain entirely from conveying the Program. - - 13. Use with the GNU Affero General Public License. - - Notwithstanding any other provision of this License, you have -permission to link or combine any covered work with a work licensed -under version 3 of the GNU Affero General Public License into a single -combined work, and to convey the resulting work. The terms of this -License will continue to apply to the part which is the covered work, -but the special requirements of the GNU Affero General Public License, -section 13, concerning interaction through a network will apply to the -combination as such. - - 14. Revised Versions of this License. - - The Free Software Foundation may publish revised and/or new versions of -the GNU General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - - Each version is given a distinguishing version number. If the -Program specifies that a certain numbered version of the GNU General -Public License "or any later version" applies to it, you have the -option of following the terms and conditions either of that numbered -version or of any later version published by the Free Software -Foundation. If the Program does not specify a version number of the -GNU General Public License, you may choose any version ever published -by the Free Software Foundation. - - If the Program specifies that a proxy can decide which future -versions of the GNU General Public License can be used, that proxy's -public statement of acceptance of a version permanently authorizes you -to choose that version for the Program. - - Later license versions may give you additional or different -permissions. However, no additional obligations are imposed on any -author or copyright holder as a result of your choosing to follow a -later version. - - 15. Disclaimer of Warranty. - - THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY -APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT -HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY -OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM -IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF -ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. Limitation of Liability. - - IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS -THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY -GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE -USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF -DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD -PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), -EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF -SUCH DAMAGES. - - 17. Interpretation of Sections 15 and 16. - - If the disclaimer of warranty and limitation of liability provided -above cannot be given local legal effect according to their terms, -reviewing courts shall apply local law that most closely approximates -an absolute waiver of all civil liability in connection with the -Program, unless a warranty or assumption of liability accompanies a -copy of the Program in return for a fee. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -state the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - -Also add information on how to contact you by electronic and paper mail. - - If the program does terminal interaction, make it output a short -notice like this when it starts in an interactive mode: - - Copyright (C) - This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, your program's commands -might be different; for a GUI interface, you would use an "about box". - - You should also get your employer (if you work as a programmer) or school, -if any, to sign a "copyright disclaimer" for the program, if necessary. -For more information on this, and how to apply and follow the GNU GPL, see -. - - The GNU General Public License does not permit incorporating your program -into proprietary programs. If your program is a subroutine library, you -may consider it more useful to permit linking proprietary applications with -the library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. But first, please read -. diff --git a/data/users.csv b/data/users.csv deleted file mode 100644 index 1d183062..00000000 --- a/data/users.csv +++ /dev/null @@ -1,4 +0,0 @@ -"ID","name","password","salt","enumerations","IPs","groups","apikey","categories","scripts" -"0","Not Authenticated","","","","*","0","","*","*" -"1","Unknow","","","","*","0,1","","*","*" -"2","Admin","pZo8c8+cKLTHFaUBxGwcYaFDgNRw9HHph4brixOo6OMusFKbfkBEObZiNwda/f9W3+IpiMY8kqiFmQcbkUCbGw==","c2FsdA==","1000","192.168.*,172.16.*,10.*,127.0.*","50,1000","AdminAdminAdminAdminAdminAdminAdminAdminAdminAdminAdminAdminAdminAdminAdminAdminAdminAdminAdminAdminAdminAdminAdminAdminAdminAdminAdminAdminAdminAdminAdminAdmin","*","*" diff --git a/doc/empty.html b/doc/empty.html deleted file mode 100644 index 724f4d4c..00000000 --- a/doc/empty.html +++ /dev/null @@ -1 +0,0 @@ -html \ No newline at end of file diff --git a/harden.py b/harden.py deleted file mode 100644 index 89948ba9..00000000 --- a/harden.py +++ /dev/null @@ -1,614 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -################### -# This tool runs CLI scripts and displays output in a Web Interface. -# Copyright (C) 2021, 2022, 2023, 2024 Maurice Lambert - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. - -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. - -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -################### - -""" -This tool runs CLI scripts and displays output in a Web Interface. - -This file hardens the WebScripts installation and configuration. -""" - -__version__ = "0.0.10" -__author__ = "Maurice Lambert" -__author_email__ = "mauricelambert434@gmail.com" -__maintainer__ = "Maurice Lambert" -__maintainer_email__ = "mauricelambert434@gmail.com" -__description__ = """ -This tool runs CLI scripts and displays output in a Web Interface. - -This file hardens the WebScripts installation and configuration. -""" -license = "GPL-3.0 License" -__url__ = "https://github.com/mauricelambert/WebScripts" - -copyright = """ -WebScripts Copyright (C) 2021, 2022, 2023, 2024 Maurice Lambert -This program comes with ABSOLUTELY NO WARRANTY. -This is free software, and you are welcome to redistribute it -under certain conditions. -""" -__license__ = license -__copyright__ = copyright - -__all__ = ["Hardening"] - -print(copyright) - -from os import name - -from os import ( - getcwd, - chmod, - remove, - mkdir, - makedirs, - environ, - listdir, -) -from os.path import ( - isdir, - dirname, - split, - splitext, - join, - abspath, - basename, - exists, - normpath, -) -from importlib.util import spec_from_loader, module_from_spec -from logging import FileHandler, Formatter, getLogger, Logger -from importlib.machinery import SourceFileLoader -from argparse import ArgumentParser, Namespace -from sys import _getframe, executable -from collections.abc import Callable -from typing import Tuple, List, Dict -from getpass import getuser -from json import load, dump -from shutil import copytree -from glob import iglob - - -if name != "nt": - from os import chown, getuid - - -def prepare(arguments: Namespace) -> Tuple[str, List[str]]: - """ - This function prepares WebScripts to be hardened. - """ - - path = dirname(__file__) - - files = [] - - for directory in (arguments.directory, path): - hardening_path = join(directory, "hardening") - log_path = join(directory, "logs") - - if not isdir(hardening_path): - mkdir(hardening_path) - if not isdir(log_path): - mkdir(log_path) - - for filename in ( - "logs_checks.json", - "uploads_file_integrity.json", - "webscripts_file_integrity.json", - "audit.html", - "audit.txt", - "audit.json", - ): - filename = join(hardening_path, filename) - files.append(filename) - if not exists(filename): - with open(filename, "w") as file: - file.write("{}") - - return path, hardening_path, log_path, files - - -def get_custom_logger(path: str, name: str = None) -> Logger: - """ - This function creates a custom logger. - """ - - name = name or _getframe().f_code.co_filename - logger = getLogger(name) - logger.propagate = False - - if not logger.handlers: - formatter = Formatter( - fmt=( - "%(asctime)s%(levelname)-9s(%(levelno)s) " - "{%(name)s - %(filename)s:%(lineno)d} %(message)s" - ), - datefmt="[%Y-%m-%d %H:%M:%S] ", - ) - file = FileHandler(join(path, name + ".log")) - file.setFormatter(formatter) - - logger.addHandler(file) - logger.setLevel(1) - - return logger - - -parser = ArgumentParser( - description="This script hardens the WebScripts installation." -) -parser.add_argument( - "--admin-password", - "-p", - "--password", - required=True, - help="The new WebScripts administrator password.", -) -# parser.add_argument( -# "--json-only", -# "-j", -# default=True, -# action="store_false", -# help="Keep only JSON configurations." -# ) -parser.add_argument( - "--owner", - "-o", - required=True, - help=( - "The user name that launches the WebScripts" - " service (a specific user for that service only).", - ), -) -parser.add_argument( - "--directory", - "-d", - required=True, - help="The directory where the WebScripts service will be launched.", -) -arguments = parser.parse_args() - -path, hardening_path, log_path, files = prepare(arguments) -logger: Logger = get_custom_logger(hardening_path, "hardening") -logger_debug: Callable = logger.debug -logger_info: Callable = logger.info -logger_warning: Callable = logger.warning -logger_error: Callable = logger.error -logger_critical: Callable = logger.critical -logger_log: Callable = logger.log - - -class Hardening: - """ - This class hardens WebScripts. - """ - - def __init__( - self, - admin_password: str = None, - json_only: bool = True, - owner: str = None, - directory: str = None, - ): - self.admin_password = admin_password - self.documentation_paths = [] - self.json_only = json_only - self.owner = owner or getuser() - self.directory = directory or getcwd() - is_windows = self.is_windows = name == "nt" - - self.is_admin = None if is_windows else (getuid() == 0) - self.json_config_files = [] - self.ini_config_files = [] - self.py_scripts_files = [] - self.owner_property = None - self.csv_files = [] - - if is_windows: - logger_warning("Permissions can not be change on Windows system.") - logger_warning("Owner can not be change on Windows system.") - else: - from pwd import getpwnam - - logger_info(f"The owner will be {owner}") - - self.owner_property = getpwnam(owner) - logger_info( - f"UID will be {self.owner_property.pw_uid}, " - f"GID will be {self.owner_property.pw_gid}" - ) - - if not self.is_admin: - logger_warning("Permissions can not be change without privileges.") - logger_warning("Owner can not be change without privileges.") - - def get_configurations( - self, filename: str, script_name: str = None - ) -> None: - """ - This method gets scripts from configurations file. - """ - - logger_debug(f"Open and loads {filename!r}") - with open(filename) as file: - configurations: Dict[str, dict] = load(file) - - scripts = configurations.get("scripts") - has_no_script = scripts is None - - if has_no_script and script_name is not None: - logger_debug("Check for specific configuration file") - script = configurations.get("script") - - if script is not None: - logger_info( - "Specific configuration file found" - f" for {script_name!r} (filename)" - ) - self.harden_script(script, script_name) - - elif not has_no_script: - for name, section_name in scripts.items(): - self.harden_script(configurations[section_name], name) - - server = configurations.get("server") - if server is not None: - self.harden_server(server, dirname(filename)) - - Hardening.save_scripts_configurations(filename, configurations) - - def get_files_from_glob_path( - self, path: List[str], globsyntax: List[str] - ) -> List[str]: - """ - This method returns all files (with absolute path) matching - a list of glob syntax. - """ - - return [abspath(x) for g in globsyntax for x in iglob(join(*path, g))] - - def harden_server(self, section: dict, directory: str) -> None: - """ - This method hardens server configuration. - """ - - logger_info("Hardens the WebScripts server configuration...") - path_ = [directory, ".."] - - if self.is_windows: - path_.insert(1, "..") - - self.documentation_paths = section["documentations_path"] = [ - normpath(abspath(join(self.directory, x))) - for x in section["documentations_path"] - ] - section["modules_path"] = [normpath(abspath(join(*path_, "modules")))] - section["data_dir"] = abspath(join(self.directory, "data")) - section["json_scripts_config"] = self.get_files_from_glob_path( - path_, section["json_scripts_config"] - ) - section["ini_scripts_config"] = self.get_files_from_glob_path( - path_, section["ini_scripts_config"] - ) - section["scripts_path"] = self.get_files_from_glob_path( - path_, section["scripts_path"] - ) - section["js_path"] = self.get_files_from_glob_path( - path_, section["js_path"] - ) - section["statics_path"] = self.get_files_from_glob_path( - path_, section["statics_path"] - ) - - def harden_script(self, section: dict, filename: str) -> None: - """ - This method hardens script configuration. - """ - - logger_info("Hardens script " + repr(filename)) - logger_debug(f"Add launcher {executable!r} for {filename!r}") - section["launcher"] = executable - specific_config_file = section.get("configuration_file") - - if specific_config_file: - specific_config_file = basename(specific_config_file) - - script_name, _ = splitext(basename(filename)) - logger_info("Configure script named: " + repr(script_name)) - for config_file in self.json_config_files: - if config_file.endswith(specific_config_file): - section["configuration_file"] = config_file - self.get_configurations(config_file, filename) - break - else: - logger_error( - "Configuration file not found for " + repr(filename) - ) - - for py_filename in self.py_scripts_files: - py_basename = basename(py_filename) - if py_basename == filename: - logger_debug( - "Add the script absolute path" - f" {py_filename!r} for {filename!r}." - ) - section["path"] = py_filename - break - else: - logger_error("Script file not found for " + repr(filename)) - - def linux_hardening_file_permissions(self) -> None: - """ - This method changes files permissions for hardening file. - """ - - pw_uid = self.owner_property.pw_uid - pw_gid = self.owner_property.pw_gid - directory = self.directory - - for file in files: - chmod(file, 0o600) - chown(file, pw_uid, pw_gid) - - logger_warning(f"Change permissions and owner of {directory}") - chmod(directory, 0o755) # nosec - chown(directory, 0, 0) - - chmod(hardening_path, 0o755) # nosec - chown(hardening_path, 0, 0) - - chmod(log_path, 0o700) # nosec - chown(log_path, pw_uid, pw_gid) - - hardening = join(directory, "hardening") - chmod(hardening, 0o755) # nosec - chown(hardening, 0, 0) - - logs = join(directory, "logs") - chmod(logs, 0o700) # nosec - chown(logs, pw_uid, pw_gid) - - for documentation_path in self.documentation_paths: - documentation_directory = dirname(documentation_path) - makedirs(documentation_directory, exist_ok=True) - chmod(documentation_directory, 0o700) # nosec - chown(documentation_directory, pw_uid, pw_gid) - - def linux_file_permissions(self, filename: str) -> None: - """ - This method changes files permissions on Linux. - """ - - owner_property = self.owner_property - owner = self.owner - - filename = abspath(filename) - file = split(filename)[1] - extension = splitext(filename)[1] - directory = dirname(filename) - - change_owner_directory: bool = True - change_permission_directory: bool = True - - logger_debug(f"Change the permissions of {filename}") - - if file == "WebScripts": - logger_debug( - f"Add the execution permissions for the owner on {filename}" - ) - chmod(filename, 0o500) - - elif directory.endswith("data/uploads") or directory.endswith( - "WebScripts/doc" - ): - logger_debug(f"Change owner for {directory} directory") - chmod(directory, 0o700) - chown( - directory, - owner_property.pw_uid, - owner_property.pw_gid, - ) - change_owner_directory = False - change_permission_directory = False - - if directory.endswith("WebScripts/doc"): - directory = directory[:-3] + "logs" - makedirs(directory, exist_ok=True) - chown( - directory, - owner_property.pw_uid, - owner_property.pw_gid, - ) - chmod(directory, 0o700) - elif file == "id" or extension == ".csv": - chmod(filename, 0o600) - else: - chmod(filename, 0o400) - - logger_debug(f"Change the owner of {filename}") - chown(filename, owner_property.pw_uid, owner_property.pw_gid) - - logger_debug( - f"Change permissions and owner on directory {directory!r}" - ) - if change_permission_directory: - chmod(directory, 0o755) # nosec - - if change_owner_directory: - chown(directory, 0, 0) - - @staticmethod - def save_scripts_configurations( - filename: str, configurations: Dict[str, dict] - ) -> None: - """ - This function saves a configuration file.json_only - """ - - logger_warning("Save new/secure configurations in " + filename) - with open(filename, "w") as file: - dump(configurations, file, indent=4) - - def remove_configuration_files(self) -> None: - """ - This function removes unnecessary configuration files. - """ - - sub_path = join("config", "nt") - - if self.json_only: - logger_info("Remove server.ini files") - ini_config_files = self.ini_config_files.copy() - - for file in ini_config_files: - logger_info("Remove INI configuration file " + repr(file)) - remove(file) - self.ini_config_files.remove(file) - - logger_debug("Research unused configuration files") - if self.is_windows: - unused_configurations = [ - f for f in self.json_config_files if sub_path not in f - ] - else: - unused_configurations = [ - f for f in self.json_config_files if sub_path in f - ] - - logger_info("Remove unused configuration files") - for file in unused_configurations: - logger_debug(f"Remove {file}.") - remove(file) - self.json_config_files.remove(file) - - def change_admin_password(self) -> None: - """ - This function change the administrator - password (default account named Admin). - """ - - if not self.admin_password: - logger_warning( - "The default administrator password is not changed (argument:" - " --admin-password/-p is not used)." - ) - return None - - logger_debug("Import manage_defaults_databases (account manager)") - module_name = "manage_defaults_databases" - file_name = module_name + ".py" - - for filename in self.py_scripts_files: - if filename.endswith(file_name): - logger_info( - "Module to change Admin password found in " - + repr(filename) - ) - loader = SourceFileLoader(module_name, filename) - break - - for filename in self.csv_files: - if filename.endswith("users.csv"): - logger_info("Users CSV database found in " + repr(filename)) - environ["WEBSCRIPTS_DATA_PATH"] = split(filename)[0] - break - # Environment variable WEBSCRIPTS_DATA_PATH is used - # in manage_defaults_databases module - - try: - spec = spec_from_loader(module_name, loader) - except UnboundLocalError: - logger_critical( - "manage_defaults_databases module not found," - " Admin password is not changed ! Installation" - " files have been modified !" - ) - return None - manage_defaults_databases = module_from_spec(spec) - loader.exec_module(manage_defaults_databases) - - manage_defaults_databases.change_user_password( - "2", self.admin_password - ) - logger_info("Administrator is changed.") - - def add_data_directory(self) -> None: - """ - This method adds a data directory - based on the WebScripts data directory. - """ - - new_data_path = join(self.directory, "data") - - if not isdir(new_data_path): - copytree(join(path, "data"), new_data_path) - - for file in iglob(join(new_data_path, "**"), recursive=True): - chown(file, self.owner_property.pw_uid, self.owner_property.pw_gid) - - def hardening(self) -> None: - """ - This function starts hardening. - """ - - logger_debug("Logging is configured") - linux_hardening: bool = not self.is_windows and self.is_admin - - for filename in iglob(join(path, "**"), recursive=True): - if linux_hardening: - self.linux_file_permissions(filename) - - extension = splitext(filename)[1] - - if extension == ".json": - logger_debug("Add JSON file " + repr(filename)) - self.json_config_files.append(filename) - - elif extension == ".py": - logger_debug("Add python file " + repr(filename)) - self.py_scripts_files.append(filename) - - elif split(filename)[1] == "server.ini": - logger_debug("Add INI file " + repr(filename)) - self.ini_config_files.append(filename) - - elif extension == ".csv": - logger_debug("Add CSV file " + repr(filename)) - self.csv_files.append(filename) - - executable_path = dirname(executable) - for filename in listdir(executable_path): - if filename == "wsgi.py" or filename == "activate_this.py": - filename = join(executable_path, filename) - if linux_hardening: - self.linux_file_permissions(filename) - self.py_scripts_files.append(filename) - - for config_file in self.json_config_files: - self.get_configurations(config_file) - - self.remove_configuration_files() - self.change_admin_password() - if linux_hardening: - self.linux_hardening_file_permissions() - self.add_data_directory() - - -Hardening(**arguments.__dict__).hardening() diff --git a/hardening.py b/hardening.py deleted file mode 100644 index 35fbbde5..00000000 --- a/hardening.py +++ /dev/null @@ -1,2338 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -################### -# This tool runs CLI scripts and displays output in a Web Interface. -# Copyright (C) 2021, 2022, 2023 Maurice Lambert - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. - -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. - -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -################### - -""" -This tool runs CLI scripts and displays output in a Web Interface. - -This file implement the hardening audit of the WebScripts installation and -configuration. -""" - -__version__ = "1.1.8" -__author__ = "Maurice Lambert" -__author_email__ = "mauricelambert434@gmail.com" -__maintainer__ = "Maurice Lambert" -__maintainer_email__ = "mauricelambert434@gmail.com" -__description__ = """ -This tool runs CLI scripts and displays output in a Web Interface. - -This file implement the hardening audit of the WebScripts installation and -configuration. -""" -license = "GPL-3.0 License" -__url__ = "https://github.com/mauricelambert/WebScripts" - -copyright = """ -WebScripts Copyright (C) 2021, 2022, 2023 Maurice Lambert -This program comes with ABSOLUTELY NO WARRANTY. -This is free software, and you are welcome to redistribute it -under certain conditions. -""" -__license__ = license -__copyright__ = copyright - -__all__ = ["Report", "Rule", "Audit", "SEVERITY", "main"] - -from os.path import ( - exists, - join, - isdir, - split, - dirname, - basename, - isfile, - isabs, -) -from os import getcwd, listdir, stat, stat_result, scandir -from sys import prefix, base_prefix, modules, executable -from tempfile import TemporaryFile, _get_default_tempdir -from typing import TypeVar, List, Set, Dict, Tuple -from time import sleep, strftime, localtime -from io import open, DEFAULT_BUFFER_SIZE -from collections.abc import Iterator -from json import load, dumps, loads -from urllib.request import urlopen -from hashlib import new as newhash -from urllib.error import URLError -from dataclasses import dataclass -from zipimport import zipimporter -from socket import gethostbyname -from pkgutil import iter_modules -from contextlib import suppress -from operator import itemgetter -from shutil import copyfileobj -from types import ModuleType -from threading import Thread -from getpass import getuser -from platform import system -from stat import filemode -from enum import Enum -from glob import glob -import ctypes -import os - -try: - import pip._internal.operations.freeze -except (ImportError, AttributeError): - PIP = False -else: - PIP = True - -ServerConfiguration = TypeVar("ServerConfiguration") -Server = TypeVar("Server") -Logs = TypeVar("Logs") -server_path = dirname(__file__) - - -class SEVERITY(Enum): - """ - Severity level of the rules. - """ - - INFORMATION = "INFORMATION" - LOW = "LOW" - MEDIUM = "MEDIUM" - HIGH = "HIGH" - CRITICAL = "CRITICAL" - - -class GETTER: - """ - This class groups getters. - """ - - sever = itemgetter("severity") - score = itemgetter("SCORING") - field = itemgetter("fields") - total = itemgetter("total") - fail = itemgetter("FAIL") - info = itemgetter(SEVERITY.INFORMATION.value) - crit = itemgetter(SEVERITY.CRITICAL.value) - med = itemgetter(SEVERITY.MEDIUM.value) - high = itemgetter(SEVERITY.HIGH.value) - low = itemgetter(SEVERITY.LOW.value) - - -@dataclass -class Rule: - """ - This class implement a rule for hardening. - """ - - subject: str - id_: int - is_OK: bool - level: int - severity: str - category: str - reason: str - - -class Report: - """ - This class implement the report object. - """ - - def __init__( - self, - rules: List[Rule], - file_integrity: List[Dict[str, str]], - server: Server, - ): - self.rules = rules - self.server = server - self.logs = server.logs - self.reports_json: str = None - self.reports_dict: str = None - self.reports_text: str = None - self.reports_html: str = None - self.file_integrity = file_integrity - - @staticmethod - def truncate_string( - string: str, length: int = 13, end: str = "...", separator: str = "," - ) -> str: - """ - This function truncate a string. - """ - - if not isinstance(string, str): - string = str(string) - - length_string = len(string) - - if length_string > length: - string = f"{string[:length - len(end)]}{end}{separator}" - else: - string = f"{string}{separator}{' ' * (length - length_string)}" - - return string - - def as_json(self) -> str: - """ - This function returns a JSON string of audit results. - """ - - G = GETTER - - scoring = {f"{s.value} total": 0 for s in SEVERITY} - scoring.update({f"{s.value} fail": 0 for s in SEVERITY}) - - scoring["total"] = 0 - scoring["FAIL"] = 0 - - self.reports_dict = { - SEVERITY.INFORMATION.value: [], - SEVERITY.LOW.value: [], - SEVERITY.MEDIUM.value: [], - SEVERITY.HIGH.value: [], - SEVERITY.CRITICAL.value: [], - "FAIL": [], - "ALL": [], - "SCORING": scoring, - "fields": [], - "file integrity": self.file_integrity, - } - - for rule in self.rules: - audit = {} - self.reports_dict["ALL"].append(audit) - self.reports_dict[rule.severity].append(audit) - scoring["total"] += rule.level - scoring[f"{rule.severity} total"] += rule.level - - if not rule.is_OK: - G.fail(self.reports_dict).append(audit) - scoring["FAIL"] += rule.level - scoring[f"{rule.severity} fail"] += rule.level - - for attribut in Rule.__dataclass_fields__.keys(): - if attribut == "is_OK": - new_attribut = "state" - new_value = "PASS" if getattr(rule, attribut) else "FAIL" - elif attribut == "id_": - new_attribut = "ID" - new_value = getattr(rule, attribut) - else: - new_attribut = attribut - new_value = getattr(rule, attribut) - - if new_attribut not in G.field(self.reports_dict): - G.field(self.reports_dict).append(new_attribut) - - audit[new_attribut] = new_value - - def sort(rule: dict) -> int: - return rule["level"] - - self.reports_dict["ALL"] = sorted( - self.reports_dict["ALL"], key=sort, reverse=True - ) - self.reports_dict["FAIL"] = sorted( - G.fail(self.reports_dict), key=sort, reverse=True - ) - - fields = self.reports_dict.pop("fields") - self.reports_json = dumps(self.reports_dict, indent=4) - self.reports_dict["fields"] = fields - - return self.reports_json - - def get_pourcent(self) -> None: - """ - This function calcul pourcent. - """ - - scoring = GETTER.score(self.reports_dict) - - self.pourcent = { - s.value: 100 - - scoring[f"{s.value} fail"] - * 100 - / (scoring[f"{s.value} total"] or 1) - for s in SEVERITY - } - self.pourcent["ALL"] = 100 - GETTER.fail(scoring) * 100 / GETTER.total( - scoring - ) - - @staticmethod - def get_HTML_table(headers: str, rules: List[Rule]) -> str: - """ - This function returns a HTML table with rule attributes as columns. - """ - - table = headers - for rule in rules: - class_ = f'class="{GETTER.sever(rule).lower()}"' - table += ( - f"" - + f"".join(str(x) for x in rule.values()) - + "" - ) - return table - - def as_html(self) -> str: - """ - This function return a HTML string of audit results. - """ - - G = GETTER - scoring = G.score(self.reports_dict) - get_HTML_table = self.get_HTML_table - - if self.reports_dict is None: - self.as_json() - - table = ( - f"{''.join(G.field(self.reports_dict))}" - "" - ) - - table_fail = get_HTML_table(table, G.fail(self.reports_dict)) - table_critical = get_HTML_table(table, G.crit(self.reports_dict)) - table_high = get_HTML_table(table, G.high(self.reports_dict)) - table_medium = get_HTML_table(table, G.med(self.reports_dict)) - table_low = get_HTML_table(table, G.low(self.reports_dict)) - table_information = get_HTML_table(table, G.info(self.reports_dict)) - - file_integrity = self.file_integrity - score_integrity = sum([x["Score"] for x in file_integrity]) - failed_integrity = len(file_integrity) - - for integrity in file_integrity: - if integrity["Score"] == 10: - integrity["severity"] = "CRITICAL" - elif integrity["Score"] >= 5: - integrity["severity"] = "HIGH" - else: - integrity["severity"] = "MEDIUM" - - if file_integrity: - table = ( - f"{''.join(file_integrity[0].keys())}" - "" - ) - table_integrity = get_HTML_table(table, file_integrity) - else: - table_integrity = ( - '

No compromised files.

' - ) - - self.reports_html = f""" - - - - - WebScripts audit - - - -

WebScripts: Hardening Audit Report

- -

Links

- - -

INTEGRITY

- -

File number: {failed_integrity} - (file should be 0)
- Score: {score_integrity} (score should be 0)

- -

SCORING

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
ScoreFailTotalCompliance (% pourcent)
All{G.fail(scoring)}{G.total(scoring)} -{self.pourcent['ALL']} -
Critical -{scoring[f"{SEVERITY.CRITICAL.value} fail"]} - -{scoring[f"{SEVERITY.CRITICAL.value} total"]} - -{G.crit(self.pourcent)} -
High -{scoring[f"{SEVERITY.HIGH.value} fail"]} - -{scoring[f"{SEVERITY.HIGH.value} total"]} - -{G.high(self.pourcent)} -
Medium -{scoring[f"{SEVERITY.MEDIUM.value} fail"]} - -{scoring[f"{SEVERITY.MEDIUM.value} total"]} - -{G.med(self.pourcent)} -
Low -{scoring[f"{SEVERITY.LOW.value} fail"]} - -{scoring[f"{SEVERITY.LOW.value} total"]} - -{G.low(self.pourcent)} -
Information -{scoring[f"{SEVERITY.INFORMATION.value} fail"]} - -{scoring[f"{SEVERITY.INFORMATION.value} total"]} - -{G.info(self.pourcent)} -
- -

FAILED

- - {table_fail} -
- -

INTEGRITY

- - - {table_integrity} -
- -

CRITICAL

- - {table_critical} -
- -

HIGH

- - {table_high} -
- -

MEDIUM

- - {table_medium} -
- -

LOW

- - {table_low} -
- -

INFORMATION

- - {table_information} -
- - - - """ - - return self.reports_html - - @staticmethod - def get_text_table( - headers: str, rules: List[Rule], joiner: str = " " - ) -> str: - """ - This function return a text table with rule attributes as columns. - """ - - return headers + "\n - ".join( - [ - joiner.join( - f"{Report.truncate_string(value)}" - for value in rule.values() - ) - for rule in rules - ] - ) - - def as_text(self) -> str: - """This function return a HTML string of audit results.""" - - if self.reports_dict is None: - self.as_json() - - G = GETTER - scoring = G.score(self.reports_dict) - tab = " " - # end = "..." - - headers = tab.join( - self.truncate_string(f) for f in Rule.__dataclass_fields__.keys() - ) - headers = f" ~ {headers}\n - " - - fail_text = self.get_text_table( - headers, G.fail(self.reports_dict), tab - ) - critical_text = self.get_text_table( - headers, G.crit(self.reports_dict), tab - ) - high_text = self.get_text_table( - headers, G.high(self.reports_dict), tab - ) - medium_text = self.get_text_table( - headers, G.med(self.reports_dict), tab - ) - low_text = self.get_text_table(headers, G.med(self.reports_dict), tab) - information_text = self.get_text_table( - headers, G.info(self.reports_dict), tab - ) - - self.reports_text = f""" - ___________________________________________ - | | - | ** WebScripts Hardening Audit Report ** | - |___________________________________________| - - 1. Scoring - - ALL: Total:\t{G.total(scoring):0>4},\ - Fail:\t{G.fail(scoring):0>4}, Pourcent:\t\ -{self.pourcent['ALL']:0>3}% - - CRITICAL: Total:\t\ -{scoring[f"{SEVERITY.CRITICAL.value} total"]:0>4}, Fail:\t\ -{scoring[f"{SEVERITY.CRITICAL.value} fail"]:0>4}, \ -Pourcent:\t{G.crit(self.pourcent):0>3}% - - HIGH: Total:\t\ -{scoring[f"{SEVERITY.HIGH.value} total"]:0>4}, Fail:\t\ -{scoring[f"{SEVERITY.HIGH.value} fail"]:0>4}, Pourcent:\t\ -{G.high(self.pourcent):0>3}% - - MEDIUM: Total:\t\ -{scoring[f"{SEVERITY.MEDIUM.value} total"]:0>4}, Fail:\t\ -{scoring[f"{SEVERITY.MEDIUM.value} fail"]:0>4}, Pourcent:\ -\t{G.med(self.pourcent):0>3}% - - LOW: Total:\t\ -{scoring[f"{SEVERITY.LOW.value} total"]:0>4}, Fail:\t\ -{scoring[f"{SEVERITY.LOW.value} fail"]:0>4}, Pourcent:\t\ -{G.low(self.pourcent):0>3}% - - INFORMATION: Total:\t{ -scoring[f"{SEVERITY.INFORMATION.value} total"]:0>4}, Fail:\ -\t{scoring[f"{SEVERITY.INFORMATION.value} fail"]:0>4}, \ - Pourcent:\t{G.info(self.pourcent):0>3}% - - 2. Failed -{fail_text} - - 3. Critical -{critical_text} - - 4. High -{high_text} - - 5. Medium -{medium_text} - - 6. Low -{low_text} - - 7. Information -{information_text} - """ - - return self.reports_text - - def notification(self) -> None: - """ - This function send an email notification - to administrator with the audit report. - """ - - if self.reports_text is None: - self.as_text() - - notification = ( - '' - "WebScripts Hardening Report
"
-            f"{self.reports_text}
" - ) - - attachments = [] - - if self.reports_html is not None: - attachments.append( - ( - [self.reports_html.encode()], - { - "maintype": "text", - "subtype": "html", - "filename": "audit.html", - }, - ) - ) - - if self.reports_json is not None: - attachments.append( - ( - [self.reports_json.encode()], - { - "maintype": "application", - "subtype": "json", - "filename": "audit.json", - }, - ) - ) - - sleep( - 60 - ) # Wait 1 minutes (else "SmtpError: to many connections" is raised) - - server = self.server - server.send_mail( - server.configuration, - notification, - title="[! WebScripts Hardening Report ]", - content_type="text/html; charset=utf-8", - attachments=attachments, - ) - - -class Audit: - """ - This function implement hardening checks. - """ - - is_windows = system() == "Windows" - current_dir = getcwd() - network_up = True - latest_ = [] - latest = [] - - def audit_in_venv(server: Server) -> Rule: - """ - This function checks the virtualenv. - """ - - return Rule( - "Virtualenv", - 0, - prefix != base_prefix, - 3, - SEVERITY.LOW.value, - "Installation", - "WebScripts is not install in virtualenv.", - ) - - def audit_config_files(server: Server) -> Rule: - """ - This function checks the configurations files. - """ - - files = [ - join("config", "server.ini"), - join("config", "server.json"), - ( - join(server_path, "config", "nt", "server.ini") - if Audit.is_windows - else join(server_path, "config", "server.ini") - ), - ( - join(server_path, "config", "nt", "server.json") - if Audit.is_windows - else join(server_path, "config", "server.json") - ), - ] - - files = [file for file in files if isfile(file)] - length = len(files) - - return Rule( - "Configurations files", - 31, - length <= 1, - 5, - SEVERITY.MEDIUM.value, - "Installation", - "WebScripts should be configured by only one configuration file " - f"({length} found: {', '.join(files)}).", - ) - - def audit_venv_modules(server: Server) -> Rule: - """ - This function checks the virtualenv modules. - """ - - venv_modules = [] - - if PIP and Audit.is_windows: - modules = [ - package - for package in pip._internal.operations.freeze.freeze() - if not package.startswith("pip==") - and not package.startswith("setuptools==") - and not package.startswith("pywin32==") - and not package.startswith("WebScripts==") - and not package.startswith("WebScriptsTools==") - and not package.startswith("pkg-resources==") - and not package.startswith("wheel==") - ] - return Rule( - "Virtualenv modules", - 32, - len(modules) == 0, - 3, - SEVERITY.LOW.value, - "Installation", - "WebScripts should be install in empty virtualenv (except " - "WebScriptsTools and pywin32), modules found: " - + ", ".join(modules) - + ".", - ) - elif PIP and not Audit.is_windows: - modules = [ - package - for package in pip._internal.operations.freeze.freeze() - if not package.startswith("pip==") - and not package.startswith("setuptools==") - and not package.startswith("WebScripts==") - and not package.startswith("WebScriptsTools==") - and not package.startswith("pkg-resources==") - and not package.startswith("wheel==") - ] - return Rule( - "Virtualenv modules", - 32, - len(modules) == 0, - 3, - SEVERITY.LOW.value, - "Installation", - "WebScripts should be install in empty virtualenv (except " - "WebScriptsTools), modules found: " + ", ".join(modules) + ".", - ) - - if Audit.is_windows: - preinstall_modules = [ - "WebScripts-script", - "activate_this", - "pywin32_postinstall", - "pywin32_testall", - "wsgi", - "WebScripts", - "WebScriptsTools", - "adodbapi", - "easy_install", - "isapi", - "pip", - "pkg_resources", - "pythoncom", - "setuptools", - "win32com", - "_win32sysloader", - "_winxptheme", - "mmapfile", - "odbc", - "perfmon", - "servicemanager", - "timer", - "win2kras", - "win32api", - "win32clipboard", - "win32console", - "win32cred", - "win32crypt", - "win32event", - "win32evtlog", - "win32file", - "win32gui", - "win32help", - "win32inet", - "win32job", - "win32lz", - "win32net", - "win32pdh", - "win32pipe", - "win32print", - "win32process", - "win32profile", - "win32ras", - "win32security", - "win32service", - "win32trace", - "win32transaction", - "win32ts", - "win32wnet", - "winxpgui", - "afxres", - "commctrl", - "dbi", - "mmsystem", - "netbios", - "ntsecuritycon", - "pywin32_bootstrap", - "pywin32_testutil", - "pywintypes", - "rasutil", - "regcheck", - "regutil", - "sspi", - "sspicon", - "win32con", - "win32cryptcon", - "win32evtlogutil", - "win32gui_struct", - "win32inetcon", - "win32netcon", - "win32pdhquery", - "win32pdhutil", - "win32rcparser", - "win32serviceutil", - "win32timezone", - "win32traceutil", - "win32verstamp", - "winerror", - "winioctlcon", - "winnt", - "winperf", - "winxptheme", - "dde", - "pywin", - "win32ui", - "win32uiole", - "uploads_management", - ] - else: - preinstall_modules = [ - "easy_install", - "pip", - "pkg_resources", - "setuptools", - "WebScripts", - "WebScriptsTools", - "activate_this", - "wsgi", - "distutils", - "uploads_management", - ] - - for module in iter_modules(): - finder = module.module_finder - - if ( - isinstance(finder, zipimporter) - and finder.archive.startswith(prefix) - and not [ - m - for m in preinstall_modules - if m in basename(finder.archive) - ] - ): - venv_modules.append(basename(finder.archive)) - elif isinstance(finder, zipimporter): - continue - elif finder.path.startswith(prefix) and not [ - m for m in preinstall_modules if m in module.name - ]: - venv_modules.append(module.name) - - return Rule( - "Virtualenv modules", - 32, - len(venv_modules) == 0, - 3, - SEVERITY.LOW.value, - "Installation", - "WebScripts should be install in empty virtualenv (except " - "WebScriptsTools pywin32 on Windows), modules found: " - + ",".join(venv_modules) - + ".", - ) - - def audit_system_user(server: Server) -> Rule: - """ - This function checks the user. - """ - - if Audit.is_windows: - is_admin = not ctypes.windll.shell32.IsUserAnAdmin() - else: - is_admin = os.getuid() - - return Rule( - "System user", - 1, - is_admin, - 9, - SEVERITY.CRITICAL.value, - "Process", - "WebScripts is launch with admin rights.", - ) - - def audit_interface(server: Server) -> Rule: - """ - This function checks the network interface. - """ - - return Rule( - "Network Interface", - 2, - gethostbyname(server.interface) == "127.0.0.1", - 9, - SEVERITY.CRITICAL.value, - "Configuration", - "Server interface is not 127.0.0.1.", - ) - - def audit_force_auth(server: Server) -> Rule: - """ - This function checks authentication is forced. - """ - - return Rule( - "Force authentication", - 14, - not server.configuration.accept_unauthenticated_user - and not server.configuration.accept_unknow_user, - 5, - SEVERITY.MEDIUM.value, - "Configuration", - "Authentication is not forced.", - ) - - def audit_active_auth(server: Server) -> Rule: - """ - This function checks authentication is enabled. - """ - - return Rule( - "Active authentication", - 15, - server.configuration.active_auth - and server.configuration.auth_script is not None, - 7, - SEVERITY.HIGH.value, - "Configuration", - "Authentication is disabled.", - ) - - def audit_limit_exclude_auth(server: Server) -> Rule: - """ - This function checks exclusions for authentication. - """ - - limit_exclusion = True - - for path_ in server.configuration.exclude_auth_paths: - if path_ not in ["/static/", "/js/"]: - limit_exclusion = False - - for page in server.configuration.exclude_auth_pages: - if page not in ["/api/", "/auth/", "/web/auth/"]: - limit_exclusion = False - - return Rule( - "Authentication exclusions", - 16, - limit_exclusion, - 5, - SEVERITY.MEDIUM.value, - "Configuration", - "Authentication exclusions is not restricted.", - ) - - def audit_webproxy_number(server: Server) -> Rule: - """ - This function checks exclusions for authentication. - """ - - return Rule( - "WebProxy number", - 36, - server.configuration.webproxy_number is not None, - 7, - SEVERITY.HIGH.value, - "Configuration", - "WebProxy number is not defined.", - ) - - def audit_security(server: Server) -> Rule: - """ - This function checks the security configuration. - """ - - return Rule( - "Security configuration", - 3, - server.security, - 6, - SEVERITY.HIGH.value, - "Configuration", - "Security configuration is not True.", - ) - - def audit_debug(server: Server) -> Rule: - """ - This function checks the debug configuration. - """ - - return Rule( - "Debug mode", - 4, - not server.debug, - 6, - SEVERITY.HIGH.value, - "Configuration", - "Debug configuration is not False.", - ) - - def audit_blacklist(server: Server) -> Rule: - """ - This function checks the blacklist configuration. - """ - - return Rule( - "Blacklist configuration", - 5, - server.configuration.auth_failures_to_blacklist is not None - and server.configuration.blacklist_time is not None, - 7, - SEVERITY.HIGH.value, - "Configuration", - "Blacklist is not configured.", - ) - - def audit_smtp_password(server: Server) -> Rule: - """ - This function checks the SMTP password protection. - """ - - return Rule( - "SMTP password protection", - 6, - getattr(server.configuration, "smtp_password", None) is None - or ( - server.configuration.smtp_starttls - or server.configuration.smtp_ssl - ), - 7, - SEVERITY.HIGH.value, - "Configuration", - "SMTP password is not protected.", - ) - - def audit_log_level(server: Server) -> Rule: - """ - This function checks the log level. - """ - - return Rule( - "Log level", - 7, - not server.configuration.log_level, - 5, - SEVERITY.MEDIUM.value, - "Configuration", - "Log level is not 0.", - ) - - def audits_module_path(server: Server) -> Rule: - """ - This function checks the modules paths. - """ - - for module_path in server.configuration.modules_path: - yield Rule( - "Module path", - 30, - isabs(module_path), - 5, - SEVERITY.MEDIUM.value, - "Configuration", - f"Module path {module_path!r} is not absolute.", - ) - - def audits_scripts_logs(server: Server) -> Iterator[Rule]: - """ - This function checks the configuration of the script log. - """ - - for script in server.pages.scripts.values(): - yield Rule( - "Command log", - 8, - script.no_password, - 1, - SEVERITY.INFORMATION.value, - "Script Configuration", - f"Script command is not logged for {script.name!r}.", - ) - - def audits_scripts_stderr_content_type(server: Server) -> Iterator[Rule]: - """ - This function checks the configuration of the script stderr content - type. - """ - - for script in server.pages.scripts.values(): - yield Rule( - "Error content type", - 9, - script.stderr_content_type == "text/plain", - 6, - SEVERITY.HIGH.value, - "Script Configuration", - "The content type of the stderr for " - f"{script.name!r} is not text/plain.", - ) - - def audits_scripts_content_type(server: Server) -> Iterator[Rule]: - """ - This function checks the configuration of the script content type. - """ - - for script in server.pages.scripts.values(): - yield Rule( - "Output content type", - 10, - script.content_type == "text/plain", - 1, - SEVERITY.INFORMATION.value, - "Script Configuration", - "The content type of the script named " - f"{script.name!r} is not text/plain.", - ) - - def audits_scripts_path(server: Server) -> Iterator[Rule]: - """ - This function checks the configuration of the script path. - """ - - for script in server.pages.scripts.values(): - yield Rule( - "Script path", - 17, - script.path_is_defined and isabs(script.path), - 7, - SEVERITY.HIGH.value, - "Script Configuration", - f"The path of {script.name!r} is not defined in configuration" - " files or is not absolute.", - ) - - delattr(script, "path_is_defined") - - def audits_launcher(server: Server) -> Iterator[Rule]: - """ - This function checks the configuration of the script launcher. - """ - - for script in server.pages.scripts.values(): - yield Rule( - "Script launcher", - 18, - script.launcher and isabs(script.launcher), - 7, - SEVERITY.HIGH.value, - "Script Configuration", - f"The path of {script.name!r} launcher is not defined in" - " configuration files or is not absolute.", - ) - - def audit_admin_account(server: Server) -> Iterator[Rule]: - """ - This function checks the admin password. - """ - - default_password = False - - with open(join(server.configuration.data_dir, "users.csv")) as file: - line = file.readline() - - while line: - if ( - "pZo8c8+cKLTHFaUBxGwcYaFDgNRw9HHph4brixOo6OMusF" - "KbfkBEObZiNwda/f9W3+IpiMY8kqiFmQcbkUCbGw==" in line - ): - default_password = True - break - line = file.readline() - - return Rule( - "Default credentials", - 11, - not default_password, - 7, - SEVERITY.HIGH.value, - "Password", - "Admin password is Admin.", - ) - - def get_owner(filename: str) -> str: - """ - This function return the owner of a file. - """ - - if Audit.is_windows: - with suppress(ImportError): - import win32security - - sid = win32security.GetFileSecurity( - filename, win32security.OWNER_SECURITY_INFORMATION - ).GetSecurityDescriptorOwner() - name, _, _ = win32security.LookupAccountSid(None, sid) - - return name - else: - from os import stat - from pwd import getpwuid - - return getpwuid(stat(filename).st_uid).pw_name - - def audits_file_owner(server: Server) -> Iterator[Rule]: - """ - This function checks the files owner. - """ - - user = getuser() - current_dir = Audit.current_dir - - simple_filenames = [] - - if Audit.is_windows: - important_filenames = [ - join(server_path, "config", "nt", "server.ini"), - join(server_path, "config", "nt", "server.json"), - ] - else: - important_filenames = [ - join(server_path, "config", "server.ini"), - join(server_path, "config", "server.json"), - ] - - important_filenames.append(join(server_path, "config", "loggers.ini")) - important_filenames.append(join(current_dir, "config", "server.ini")) - important_filenames.append(join(current_dir, "config", "server.json")) - - important_filenames += listdir("logs") - important_filenames += listdir(server.configuration.data_dir) - - # for dirname_ in (server_path, current_dir): - - for file in server.pages.js_paths.values(): - if exists(file.path): - simple_filenames.append(file.path) - - for file in server.pages.statics_paths.values(): - if exists(file.path): - simple_filenames.append(file.path) - - for script in server.pages.scripts.values(): - important_filenames.append(script.path) - - for module in server.pages.packages.__dict__.values(): - if isinstance(module, ModuleType): - important_filenames.append(module.__file__) - - for filename in important_filenames: - if exists(filename): - yield Rule( - "File owner", - 11, - user == Audit.get_owner(filename), - 10, - SEVERITY.CRITICAL.value, - "Files", - f"File owner is not {user!r} for {filename!r}.", - ) - - for filename in simple_filenames: - if exists(filename): - yield Rule( - "File owner", - 11, - user == Audit.get_owner(filename), - 4, - SEVERITY.MEDIUM.value, - "Files", - f"File owner is not {user!r} for {filename!r}.", - ) - - def get_permissions(filename: str) -> str: - """ - This function returns the file permissions. - """ - - return filemode(stat(filename).st_mode) - - def _audits_directory_permissions( - server: Server, secure_paths: Set[str] - ) -> Iterator[Rule]: - """ - This function checks owner and permissions on bin directory. - """ - - if Audit.is_windows: - yield Rule( - "Directory permissions", - 33, - False, - 1, - SEVERITY.INFORMATION.value, - "Files", - "Directory permissions is not check on Windows.", - ) - return - - config_path = join(server_path, "config") - secure_paths = { - join(prefix, "bin"), - config_path, - join(config_path, "files"), - join(config_path, "scripts"), - Audit.current_dir, - } - secure_paths.update(secure_paths) - - for path_ in secure_paths: - yield Rule( - "Directory owner", - 33, - Audit.get_owner(path_) == "root", - 10, - SEVERITY.CRITICAL.value, - "Files", - f"Directory owner for {path_!r} is not root.", - ) - - yield Rule( - "Directory permissions", - 34, - Audit.get_permissions(path_) == "drwxr-xr-x", - 10, - SEVERITY.CRITICAL.value, - "Files", - f"Directory permissions for {path_!r}" - " is not 755 (drwxr-xr-x).", - ) - - def audits_timeout(server: Server) -> Iterator[Rule]: - """ - This function checks scripts timeout. - """ - - for script in server.pages.scripts.values(): - yield Rule( - "Script timeout", - 35, - script.timeout, - 5, - SEVERITY.MEDIUM.value, - "Script Configuration", - f"The {script.name!r} timeout is not defined.", - ) - - def audits_file_rights(server: Server) -> Iterator[Rule]: - """ - This function checks the files rights. - """ - - if Audit.is_windows: - yield Rule( - "File permissions", - 12, - False, - 1, - SEVERITY.INFORMATION.value, - "Files", - "Files rights is not check on Windows.", - ) - return None - - current_dir = Audit.current_dir - - rw_filenames = listdir(server.configuration.data_dir) + listdir( - join(current_dir, "logs") - ) - - server_logs = join(server_path, "logs") - if exists(server_logs): - rw_filenames.extend(listdir(server_logs)) - - r_filenames = [ - join(server_path, "config", "server.ini"), - join(server_path, "config", "server.json"), - ] - - r_filenames.append(join(server_path, "config", "loggers.ini")) - r_filenames.append(join(current_dir, "config", "server.ini")) - r_filenames.append(join(current_dir, "config", "server.json")) - - for globsyntax in server.configuration.json_scripts_config: - r_filenames.extend(glob(globsyntax)) - - for globsyntax in server.configuration.ini_scripts_config: - r_filenames.extend(glob(globsyntax)) - - r_filenames += [ - file.path - for file in server.pages.js_paths.values() - if exists(file.path) - ] - - r_filenames += [ - file.path - for file in server.pages.statics_paths.values() - if exists(file.path) - ] - - executable_filenames = [ - script.path for script in server.pages.scripts.values() - ] - - executable_filenames += [ - module.__file__ - for module in server.pages.packages.__dict__.values() - if isinstance(module, ModuleType) - ] - - yield from Audit._audits_directory_permissions( - server, - { - dirname(path_) - for path_ in executable_filenames + rw_filenames + r_filenames - if exists(path_) - }, - ) - - yield from [ - Rule( - "File permissions (rw-)", - 12, - Audit.get_permissions(filename) - in ("-rw-------", "-r--------"), - 10, - SEVERITY.CRITICAL.value, - "Files", - f"File rights for {split(filename)[1]!r} is " - "not 600 (rw- --- ---).", - ) - for filename in rw_filenames - if isfile(filename) - ] - - yield from [ - Rule( - "File permissions (r--)", - 12, - Audit.get_permissions(filename) == "-r--------", - 10, - SEVERITY.CRITICAL.value, - "Files", - f"File rights for {split(filename)[1]!r} is " - "not 400 (r-- --- ---).", - ) - for filename in r_filenames - if isfile(filename) - ] - - for filename in executable_filenames: - if isfile(filename): - permissions = Audit.get_permissions(filename) - - yield Rule( - "File permissions (r-x)", - 12, - permissions.endswith("------") and "w" not in permissions, - 10, - SEVERITY.CRITICAL.value, - "Files", - f"File rights for {split(filename)[1]!r} is not 0 " - "for group and 0 for other (r-x --- ---).", - ) - - def audit_export_configuration(server: Server) -> Rule: - """ - This function checks the export configuration file. - """ - - return Rule( - "Export file", - 13, - not exists("export_Configuration.json"), - 4, - SEVERITY.MEDIUM.value, - "Files", - "The export configuration file exist, " - "should be deleted on production.", - ) - - def audits_script_configuration_file_path( - server: Server, - ) -> Iterator[Rule]: - """ - This function checks the scripts configurations files path - for absolute path configurations. - """ - - for globsyntax in server.configuration.json_scripts_config: - yield Rule( - "Configurations files", - 19, - isabs(globsyntax) and isfile(globsyntax), - 7, - SEVERITY.HIGH.value, - "Script Configuration", - f"The configuration for configuration files: {globsyntax!r}," - " is not absolute or is not an existing file (probably a glob " - "syntax or file doesn't exists).", - ) - - for globsyntax in server.configuration.ini_scripts_config: - yield Rule( - "Configurations files", - 19, - isabs(globsyntax) and isfile(globsyntax), - 7, - SEVERITY.HIGH.value, - "Script Configuration", - f"The configuration for configuration files: {globsyntax!r}," - " is not absolute or is not an existing file (probably a glob " - "syntax or file doesn't exists).", - ) - - def log_rule(rule: Rule, logs: Logs) -> None: - """ - This function log rule. - """ - - if rule.is_OK: - state = "PASS" - else: - state = "FAIL" - - log = ( - f"Audit -> state: {state}, ID: {rule.id_}, severity: " - f"{rule.severity}, level: {rule.level}, category: {rule.category}" - f', subject: "{rule.subject}", reason: "{rule.reason}".' - ) - - if rule.is_OK or SEVERITY.INFORMATION.value == rule.severity: - logs.debug(log) - elif SEVERITY.LOW.value == rule.severity: - logs.info(log) - elif SEVERITY.MEDIUM.value == rule.severity: - logs.warning(log) - elif SEVERITY.HIGH.value == rule.severity: - logs.error(log) - elif SEVERITY.CRITICAL.value == rule.severity: - logs.critical(log) - - def run(server: Server) -> List[Rule]: - """ - This function run audit and checks. - """ - - rules = [] - logs = server.logs - - for audit in dir(Audit): - if audit.startswith("audit_"): - rule = getattr(Audit, audit)(server) - Audit.log_rule(rule, logs) - rules.append(rule) - elif audit.startswith("audits_"): - for rule in getattr(Audit, audit)(server): - Audit.log_rule(rule, logs) - rules.append(rule) - - return rules - - def check_for_updates(logs: Logs) -> None: - """ - This function runs in a thread indefinitely, it checks the version - and the latest published version of WebScripts every hour. - If the version and the latest published version are different, - this function sends an email notification. - """ - - latest_ = Audit.latest - - if __package__: - str_version = modules[__package__].__version__ - else: - try: - from __init__ import __version__ as str_version - except ImportError: - from .__init__ import __version__ as str_version - - version = [int(i) for i in str_version.split(".")] - - def get_latest() -> str: - """ - This function request github api and return the latest version. - """ - - logs.debug( - "Request github API to get latest version of WebScripts..." - ) - response = urlopen( # nosec - "https://api.github.com/repos/mauricelambert/WebScripts/tags" - ) - - str_latest = load(response)[0]["name"][1:] - return str_latest, [int(i) for i in str_latest.split(".")] - - if Audit.network_up: - try: - str_latest, latest = str_latest, Audit.latest = get_latest() - except URLError: - logs.critical("Network error: updates are not checked.") - Audit.network_up = False - return "" - - if version < latest and latest != latest_: - logs.critical( - "WebScripts is not up-to-date, current:" - f" {str_version} latest: {str_latest}" - ) - return ( - f"Current WebScripts version: {str_version}\n" - f"Latest WebScripts version: {str_latest}.\nIt is " - "recommended that you upgrade your WebScripts server." - ) - - return "" - - -class FilesIntegrity: - """ - This class checks the file integrity. - """ - - def __init__(self, server: Server): - self.logs = server.logs - self.temp = _get_default_tempdir() - - self.webscripts_filename = self.get_hardening_filename( - "webscripts_file_integrity.json" - ) - - self.uploads_filename = self.get_hardening_filename( - "uploads_file_integrity.json" - ) - - self.logs_filename = self.get_hardening_filename("logs_checks.json") - - self.temp_logs_files = {} - self.server = server - self.hashes = {} - - def get_hardening_filename(self, filename: str) -> str: - """ - This function returns the path of the filename. - """ - - filenames = ( - join(Audit.current_dir, "hardening", filename), - join(server_path, "hardening", filename), - ) - - for path in filenames: - if exists(path): - _filename = path - break - else: - self.logs.critical( - repr(filename) + " not found ! " - "Build it in temp file, is not recommended !" - " Install is not secure !" - ) - - try: - _filename = filenames[0] - with open(_filename, "wb") as file: - file.write(b"{}") - except PermissionError: - _filename = join(self.temp, filename) - if not exists(_filename): - with open(_filename, "wb") as file: - file.write(b"{}") - - return _filename - - def get_old_files( - self, filename: str, hash_: str - ) -> Tuple[Dict[str, str], Dict[str, Dict[str, str]]]: - """ - This function returns file data to check integrity - from JSON file (previous check). - """ - - logs = self.logs - logs.debug("Get old file data for integrity checks.") - to_yield = None - - with open(filename, "rb") as file: - data = file.read() - files = loads(data) - if hash_ is not None: - if newhash("sha512", data).hexdigest() != hash_: - logs.critical("File for integrity checks is compromised.") - to_yield = { - "File": filename, - "Reason": "File for integrity checks is compromised.", - "Score": 10, - } - - return to_yield, files - - def get_files(self) -> Dict[str, Dict[str, str]]: - """ - This functions gets used files. - """ - - def build_file(path: str) -> Dict[str, str]: - """ - This function makes file JSON object from path. - """ - - file = open(path, "rb") - data = file.read() - file.close() - - self.logs.debug(f"Get hashes and metadata for {path}.") - metadata = stat(path) - - return { - "path": path, - "sha512": newhash("sha512", data).hexdigest(), - "sha3_512": newhash("sha3_512", data).hexdigest(), - "size": metadata.st_size, - "modification": strftime( - "%Y-%m-%d %H:%M:%S", localtime(metadata.st_mtime) - ), - } - - server = self.server - pages = server.pages - configuration = server.configuration - self.webscripts_new_files = files = {} - self.logs.info("Get hash and metadata from code files...") - - templates = [] - - for name, callable_file in pages.js_paths.items(): - if not templates: - templates = [ - callable_file.template_index_path, - callable_file.template_script_path, - callable_file.template_header_path, - callable_file.template_footer_path, - ] - files[f"js {name}"] = build_file(callable_file.path) - - for name, callable_file in pages.statics_paths.items(): - if not templates: - templates = [ - callable_file.template_index_path, - callable_file.template_script_path, - ] - files[f"static {name}"] = build_file(callable_file.path) - - for name, config in pages.scripts.items(): - files[f"script {name}"] = build_file(config.path) - - for path in glob(join(server_path, "*.py")): - files[f"webscript {basename(path)}"] = build_file(path) - - for path in [ - join("config", "server.ini"), - join("config", "server.json"), - join("config", "nt", "server.ini"), - join("config", "nt", "server.json"), - join(server_path, "config", "server.ini"), - join(server_path, "config", "server.json"), - join(server_path, "config", "nt", "server.ini"), - join(server_path, "config", "nt", "server.json"), - ]: - if exists(path): - files[f"server configuration {basename(path)}"] = build_file( - path - ) - - for path in configuration.configuration_files: - files[f"script configuration {basename(path)}"] = build_file(path) - - for template in templates: - files[f"template {basename(template)}"] = build_file(template) - - for name, module in pages.packages.__dict__.items(): - if isinstance(module, ModuleType): - files[f"modules {name}"] = build_file( - module._webscripts_filepath - ) - - for script in scandir(dirname(executable)): - if script.is_file(): - files[f"venv scripts/bin {script.name}"] = build_file( - script.path - ) - - for base in (server_path, "."): - for directory in configuration.cgi_path: - full_path = join(base, directory) - if not exists(full_path): - continue - - for bin_ in scandir(full_path): - if not bin_.is_file(): - continue - - files[f"cgi-bin {bin_.name}"] = build_file(bin_.path) - - return files - - def check_webscripts_file_integrity(self) -> Iterator[Dict[str, str]]: - """ - This function compares old files data to new files data. - """ - - new_files_bak = self.get_files() - new_files = new_files_bak.copy() - self.logs.debug("Compare metadata and hashes for code files.") - to_yield, old_files = self.get_old_files( - self.webscripts_filename, self.hashes.get("webscripts") - ) - - if to_yield: - yield to_yield - - for file, data in old_files.items(): - new_data = new_files.pop(file, {}) - - for check, old_default, new_default, score in ( - ("path", "??", "::", 10), # default values are invalid. - # A hacker can make new file with different path when - # you have a weak configuration (WebScripts server - # research files). - ("sha512", "ZZ", "XX", 10), - ("sha3_512", "ZZ", "XX", 10), - ("size", -1, -2, 10), - # if the file changes, these three checks should fail. - ("modification", "~~", "!!", 10), - ): - old = data.get(check, old_default) - new = new_data.get(check, new_default) - - if old != new: - self.logs.critical(f"Code file: {file!r} is compromised.") - yield { - "File": file, - "Reason": ( - f"New {check} for {file!r} " - f"(Old: {old if old != old_default else None}," - f" New: {new if new != new_default else None})" - ), - "Score": score, - } - - for file, data in new_files.items(): - self.logs.critical(f"Code file: {file!r} is compromised.") - yield { - "File": file, - "Reason": ( - f"A new file is detected: {file!r} " - f"(Last modification: {data.get('modification')})" - ), - "Score": 5, - } - - def check_data_file_integrity(self) -> Iterator[Dict[str, str]]: - """ - This function checks uploads file integrity - (uploads can not be changed from WebScripts Server). - """ - - data_files = self.server.configuration.data_dir - uploads_files = join(data_files, "uploads") - self.new_data_files = new_data_files = {} - to_yield, old_files = self.get_old_files( - self.uploads_filename, self.hashes.get("uploads") - ) - - if to_yield: - yield to_yield - - for file in scandir(data_files): - if not file.is_file(): - continue - - name = f"Data {file.name}" - path = file.path - data = old_files.pop(name, None) - hash_ = sha512sum(path) - metadata = stat(path) - size = metadata.st_size - modif = strftime("%Y-%m-%d %H:%M:%S", localtime(metadata.st_mtime)) - - new_data_files[name] = { - "size": size, - "hash": hash_, - "modification": modif, - } - - if data is None: - self.logs.info( - f"A new database file has been created ({name})." - ) - yield { - "File": name, - "Reason": "A new database file has been created.", - "Score": 1, - } - continue - - if ( - data.get("size", -1) != size - or data.get("modification", "") != modif - or hash_ != data.get("hash", "") - ): - self.logs.warning(repr(name) + " has been modified.") - yield { - "File": name, - "Reason": "A database has been modified.", - "Score": 2, - } - - for filename in listdir(uploads_files): - path = join(uploads_files, filename) - filename = f"Uploads {filename}" - data = old_files.pop(filename, None) - hash_ = sha512sum(path) - metadata = stat(path) - size = metadata.st_size - modif = strftime("%Y-%m-%d %H:%M:%S", localtime(metadata.st_mtime)) - - new_data_files[filename] = { - "size": size, - "hash": hash_, - "modification": modif, - } - - if data is None: - self.logs.info( - f"An uploads file has been created ({filename})." - ) - yield { - "File": filename, - "Reason": "An uploads file has been created.", - "Score": 1, - } - continue - - if ( - data.get("size", -1) != size - or data.get("modification", -1) != modif - or hash_ != data.get("hash", "") - ): - self.logs.critical( - "An uploads file has been modified (this is a suspicious " - "action, check that your server is not compromised)" - f" [{filename}]." - ) - yield { - "File": filename, - "Reason": ( - "An uploads file has been modified (this is a " - "suspicious action, check that your server is not " - "compromised)." - ), - "Score": 10, - } - - for file in old_files: - self.logs.critical( - "An data/uploads file is deleted (this is a suspicious action," - f" check that your server is not compromised) [{file}]." - ) - yield { - "File": file, - "Reason": ( - "A data/uploads is deleted (this is a suspicious action," - " check that your server is not compromised)." - ), - "Score": 10, - } - - def check_logs_files(self) -> Iterator[Dict[str, str]]: - """ - This function checks logs file. - """ - - self.logs.debug("Log files integrity checks...") - temp_logs_files = self.temp_logs_files - check_logs_ok = self.check_logs_ok - _, self.logs_checks = to_yield, logs_checks = self.get_old_files( - self.logs_filename, self.hashes.get("logs") - ) - - if to_yield: - yield to_yield - - files = [logfile for logfile in self.server.configuration.log_files] - - for filename in files: - if isfile(filename): - self.logs.debug(f"Check log file: '{filename}'...") - metadata = stat(filename) - size = metadata.st_size - modification = strftime( - "%Y-%m-%d %H:%M:%S", localtime(metadata.st_mtime) - ) - creation = strftime( - "%Y-%m-%d %H:%M:%S", localtime(metadata.st_ctime) - ) - temp_file = temp_logs_files.get(filename) - - if temp_file is None: - temp_file = temp_logs_files[filename] = TemporaryFile() - - if not check_logs_ok(filename, temp_file, metadata): - file_checks = logs_checks.get(filename, {}) - self.logs.warning( - "A log file has lost logs (check log rotation " - "otherwise your server is compromised)." - ) - yield { - "File": f"Logs {filename}", - "Reason": ( - f"{filename}: logs has been modified. " - f"Last size: {file_checks.get('size')}," - f" new size: {size}. Last creation: " - f"{file_checks.get('created')}, last modification:" - f" {file_checks.get('modification')}, new creation" - f": {creation}, new modification: {modification}. " - "If it's not rotating logs, your server is" - " compromised." - ), - "Score": 7, - # score 7 because it will be reported on - # logs file rotation - } - - logs_checks[filename] = { - "modification": modification, - "created": creation, - "size": size, - } - - self.logs.debug(f"Log file: '{filename}' is checked.") - - def save(self) -> None: - """ - This function saves data to check file integrity. - """ - - data_webscripts = dumps(self.webscripts_new_files).encode() - with open(self.webscripts_filename, "wb") as file: - file.write(data_webscripts) - - data_uploads = dumps(self.new_data_files).encode() - with open(self.uploads_filename, "wb") as file: - file.write(data_uploads) - - data_logs = dumps(self.logs_checks).encode() - with open(self.logs_filename, "wb") as file: - file.write(data_logs) - - self.hashes = { - "webscripts": newhash("sha512", data_webscripts).hexdigest(), - "uploads": newhash("sha512", data_uploads).hexdigest(), - "logs": newhash("sha512", data_logs).hexdigest(), - } - - @staticmethod - def check_logs_ok( - path: str, temp_file: TemporaryFile, metadata: stat_result - ) -> bool: - """ - This function checks the log file integrity. - """ - - def restore(temp_file): - temp_file.seek(0) - with open(path, "rb+") as log_file: - log_file.seek(0) - copyfileobj(log_file, temp_file) - - temp_file.truncate() - - return_value: bool = True - temp_metadata = stat(temp_file.name) - temp_file.seek(0) - read_check_log = temp_file.readline - write_check_log = temp_file.write - - if temp_metadata.st_size > metadata.st_size: - restore(temp_file) - return False - - with open(path, "rb+") as log_file: - read_log = log_file.readline - log = read_log() - check_log = read_check_log() - - while check_log: - if log != check_log: - return_value = False - - log = read_log() - check_log = read_check_log() - - if not return_value: - restore(temp_file) - - while log: - write_check_log(log) - log = read_log() - - return return_value - - -def sha512sum(path: str, length: int = DEFAULT_BUFFER_SIZE) -> str: - """ - This function returns the SHA512 of a file. - """ - - hash_ = newhash("sha512") - update = hash_.update - with open(path, mode="rb") as file: - read = file.read - data = read(length) - - while data: - update(data) - data = read(length) - - return hash_.hexdigest() - - -def daemon_func( - server: Server, - file_integrity: FilesIntegrity, -) -> None: - """ - This function implements a daemon thread to - check WebScripts version, update and integrity. - """ - - sleep(120) # Bypass SMTP error: "SmtpError: to many connections" - logs = server.logs - files = [] - text = "" - content_type = None - new_line = "\n" - - while True: - text = ( - Audit.check_for_updates(logs) - if not text - else text.replace( - "", - "

" - + Audit.check_for_updates(logs).replace(new_line, "
") - + "

", - ) - ) - - if text: - server.send_mail( - server.configuration, - text, - title="[! WebScripts Alerts ]", - content_type=content_type, - ) - content_type = None - - sleep(3600) - - files += [ - *file_integrity.check_webscripts_file_integrity(), - *file_integrity.check_logs_files(), - *file_integrity.check_data_file_integrity(), - ] - - file_integrity.save() - - if any([file["Score"] >= 5 for file in files]): - lengths = { - "File": {"length": 24}, - "Reason": {"length": 75}, - "Score": {"length": 5}, - } - text = ( - "
"
-                + "\n".join(
-                    [
-                        "".join(
-                            [
-                                Report.truncate_string(key, **lengths[key])
-                                for key in files[0].keys()
-                            ]
-                        ),
-                        *[
-                            "".join(
-                                [
-                                    Report.truncate_string(
-                                        value, **lengths[key]
-                                    )
-                                    for key, value in file.items()
-                                ]
-                            )
-                            for file in files
-                        ],
-                    ]
-                )
-                + "
" - ) - files = [] - content_type = "text/html; charset=utf-8" - else: - text = "" - - -def get_files_recursive(path: str) -> Iterator[Tuple[str, str]]: - """ - This function returns path and file names recursively. - """ - - if not isdir(path): - return None - - for file in listdir(path): - if isfile(file): - yield join(path, file), file - elif isdir(file): - yield from get_files_recursive(join(path, file)) - - -def main(server: Server) -> Report: - """ - The main function to perform WebScripts Server hardening audit. - """ - - logs: Logs = server.logs - - file_integrity = FilesIntegrity(server) - - files = [ - *file_integrity.check_webscripts_file_integrity(), - *file_integrity.check_logs_files(), - *file_integrity.check_data_file_integrity(), - ] - - file_integrity.save() - - rules = Audit.run(server) - report = Report(rules, files, server) - report.as_json() - report.get_pourcent() - report.as_html() - report.as_text() - - with open( - file_integrity.get_hardening_filename("audit.json"), "w" - ) as file: - file.write(report.reports_json) - - with open( - file_integrity.get_hardening_filename("audit.html"), "w" - ) as file: - file.write(report.reports_html) - - with open(file_integrity.get_hardening_filename("audit.txt"), "w") as file: - file.write(report.reports_text) - - logs.debug("Start a thread to send hardening report...") - Thread(target=report.notification).start() - - logs.debug("Start the daemon to check WebScripts version and integrity...") - daemon = Thread( - target=daemon_func, - args=( - server, - file_integrity, - ), - daemon=True, - ) - daemon.start() - - return report diff --git a/modules/Configurations.py b/modules/Configurations.py deleted file mode 100644 index 8ae58572..00000000 --- a/modules/Configurations.py +++ /dev/null @@ -1,350 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -################### -# This tool runs CLI scripts and displays output in a Web Interface. -# Copyright (C) 2021, 2022, 2023 Maurice Lambert - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. - -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. - -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -################### - -""" -This tool runs CLI scripts and displays output in a Web Interface. - -This file implements a debug mode module to changes configurations -and reload modules. -""" - -__version__ = "0.0.1" -__author__ = "Maurice Lambert" -__author_email__ = "mauricelambert434@gmail.com" -__maintainer__ = "Maurice Lambert" -__maintainer_email__ = "mauricelambert434@gmail.com" -__description__ = """ -This tool runs CLI scripts and displays output in a Web Interface. - -This file implements a debug mode module to changes configurations -and reload modules. -""" -__license__ = "GPL-3.0 License" -__url__ = "https://github.com/mauricelambert/WebScripts" - -copyright = """ -WebScripts Copyright (C) 2021, 2022, 2023 Maurice Lambert -This program comes with ABSOLUTELY NO WARRANTY. -This is free software, and you are welcome to redistribute it -under certain conditions. -""" -license = __license__ -__copyright__ = copyright - -from importlib.util import spec_from_file_location # , _find_spec_from_path -from typing import TypeVar, Dict, List, Tuple -from importlib import reload as module_reload -from json.decoder import JSONDecodeError -from collections import defaultdict -from urllib.parse import parse_qs -from contextlib import suppress -from urllib.parse import quote -from json import dumps, loads -from types import ModuleType -from os import _Environ -from html import escape - -# from sys import modules - -try: - from _frozen_importlib import _exec # ModuleSpec, _init_module_attrs, -except ImportError: - from importlib._bootstrap import _exec # ModuleSpec, _init_module_attrs, - -Server = TypeVar("Server") -User = TypeVar("User") - -NoneType = type(None) - -# def my_exec(spec: ModuleSpec, module: ModuleType): -# _init_module_attrs(spec, module, override=True) -# if not hasattr(spec.loader, 'exec_module'): -# spec.loader.load_module(name) -# else: -# spec.loader.exec_module(module) - - -class Reload: - html: str = """ - WebScripts Server configuration -
{}

-
-
-
-
- """ - - accept_types: Tuple[type] = (list, NoneType, str, bool, int, float) - simple_types: Tuple[type] = (NoneType, str, bool, int, float) - - def server( - environ: _Environ, - user: User, - server: Server, - name: str, - arguments: Dict[str, Dict[str, str]], - inputs: List[str], - csrf_token: str = None, - ) -> Tuple[str, Dict[str, str], str]: - """ - This function reloads the configuration. - """ - - if arguments and isinstance(arguments, bytes): - configuration = server.configuration - builder = configuration.build_type - values = parse_qs(arguments.decode("ascii", "ignore")) - for name, values in values.items(): - value = values[-1] - with suppress(JSONDecodeError): - value = loads(value) - builder(name, value) - - accept_types = Reload.accept_types - html = Reload.html.format( - dumps( - { - k: v - for k, v in server.configuration.__dict__.items() - if isinstance(v, accept_types) - }, - indent=4, - ) - ) - - return "200 OK", {}, html - - def scripts( - environ: _Environ, - user: User, - server: Server, - name: str, - arguments: Dict[str, Dict[str, str]], - inputs: List[str], - csrf_token: str = None, - ) -> Tuple[str, Dict[str, str], str]: - """ - This function reloads a script configuration. - """ - - script = server.pages.scripts.get(name) - - if script is None: - return "404", {}, None - - if arguments and isinstance(arguments, bytes): - builder = script.build_type - values = parse_qs(arguments.decode("ascii", "ignore")) - for name, values in values.items(): - value = values[-1] - with suppress(JSONDecodeError): - value = loads(value) - builder(name, value) - - simple_types = Reload.simple_types - html = Reload.html.format( - dumps( - { - k: v - for k, v in script.get_dict().items() - if isinstance(v, simple_types) - or ( - isinstance(v, list) - and all(isinstance(x, simple_types) for x in v) - ) - }, - indent=4, - ) - ) - - return "200 OK", {}, html - - def arguments( - environ: _Environ, - user: User, - server: Server, - name: str, - arguments: Dict[str, Dict[str, str]], - inputs: List[str], - csrf_token: str = None, - ) -> Tuple[str, Dict[str, str], str]: - """ - This function reloads a argument configuration. - """ - - if "|" not in name: - return "404", {}, None - - script_name, argument_name = name.split("|", 1) - script = server.pages.scripts.get(script_name) - - if script is None: - return "404", {}, None - - argument = [a for a in script.args if argument_name == a.name] - - if not argument: - return "404", {}, None - - argument = argument[0] - - if arguments and isinstance(arguments, bytes): - builder = argument.build_type - values = parse_qs(arguments.decode("ascii", "ignore")) - for name, values in values.items(): - value = values[-1] - with suppress(JSONDecodeError): - value = loads(value) - builder(name, value) - - # simple_types = Reload.simple_types - html = Reload.html.format(dumps(argument.get_dict(), indent=4)) - - return "200 OK", {}, html - - def modules( - environ: _Environ, - user: User, - server: Server, - name: str, - arguments: Dict[str, Dict[str, str]], - inputs: List[str], - csrf_token: str = None, - ) -> Tuple[str, Dict[str, str], str]: - """ - This function adds new modules. - """ - - server.add_module_or_package() - return ( - "200 OK", - {"Content-Type": "text/plain"}, - "Modules successfully reloaded.", - ) - - def web( - environ: _Environ, - user: User, - server: Server, - name: str, - arguments: Dict[str, Dict[str, str]], - inputs: List[str], - csrf_token: str = None, - ) -> Tuple[str, Dict[str, str], str]: - """ - This function reloads web files (JS, CSS and HTML). - """ - - server.add_paths() - file = server.CommonsClasses.CallableFile - research_filename = server.research_filename - research_file_content = server.research_file_content - - template_script_path = file.template_script_path = research_filename( - "static/templates/script.html" - ) - template_header_path = file.template_header_path = research_filename( - "static/templates/header.html" - ) - template_footer_path = file.template_footer_path = research_filename( - "static/templates/footer.html" - ) - template_index_path = file.template_index_path = research_filename( - "static/templates/index.html" - ) - - print( - template_script_path, - template_header_path, - template_footer_path, - template_index_path, - ) - - file.template_script: str = research_file_content(template_script_path) - file.template_header: str = research_file_content(template_header_path) - file.template_footer: str = research_file_content(template_footer_path) - file.template_index: str = research_file_content(template_index_path) - - return ( - "200 OK", - {"Content-Type": "text/plain"}, - "Web files successfully reloaded.", - ) - - def module( - environ: _Environ, - user: User, - server: Server, - name: str, - arguments: Dict[str, Dict[str, str]], - inputs: List[str], - csrf_token: str = None, - ) -> Tuple[str, Dict[str, str], str]: - """ - This function reloads a module. - """ - - packages = server.pages.packages.__dict__ - module = packages.get(name) - print(module) - - if module is not None: - try: - packages[name] = module_reload(module) - print("Reload", module) - except ModuleNotFoundError: - print(module._webscripts_filepath, name) - spec = spec_from_file_location( - module._webscripts_filepath, name, loader=module.__loader__ - ) - # spec = _find_spec_from_path(name, module._webscripts_filepath) - spec.name = name - # my_exec(spec, module) - print(name, module) - _exec(spec, module) - packages[name] = module - - server.pages_cache = defaultdict(lambda: (None, None)) - return ( - "200 OK", - {"Content-Type": "text/plain"}, - "Module successfully reloaded.", - ) - - html = "
    " - for name, module in packages.items(): - if isinstance(module, ModuleType): - html += ( - f'
  • Reload: {escape(name)}' - "
  • " - ) - - return ( - "200 OK", - {}, - html + "
", - ) # + '
'.join(repr(x) for x in modules.keys()) diff --git a/modules/JsonRpc.py b/modules/JsonRpc.py deleted file mode 100644 index 1a7b8bb9..00000000 --- a/modules/JsonRpc.py +++ /dev/null @@ -1,197 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -################### -# This tool runs CLI scripts and displays output in a Web Interface. -# Copyright (C) 2021, 2022, 2023 Maurice Lambert - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. - -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. - -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -################### - -""" -This tool runs CLI scripts and displays output in a Web Interface. - -This module implements JSON RPC on WebScripts. -""" - -__version__ = "1.0.0" -__author__ = "Maurice Lambert" -__author_email__ = "mauricelambert434@gmail.com" -__maintainer__ = "Maurice Lambert" -__maintainer_email__ = "mauricelambert434@gmail.com" -__description__ = """ -This tool runs CLI scripts and displays output in a Web Interface. - -This module implements JSON RPC on WebScripts. -""" -__license__ = "GPL-3.0 License" -__url__ = "https://github.com/mauricelambert/WebScripts" - -copyright = """ -WebScripts Copyright (C) 2021, 2022, 2023 Maurice Lambert -This program comes with ABSOLUTELY NO WARRANTY. -This is free software, and you are welcome to redistribute it -under certain conditions. -""" -license = __license__ -__copyright__ = copyright - -__all__ = ["JsonRpc"] - -from typing import TypeVar, List, Tuple, Dict, Any, Union -from collections.abc import Callable -from os import _Environ -from json import dumps - -Server = TypeVar("Server") -User = TypeVar("User") - - -def test_call() -> int: - return 0 - - -def test_argument_list(*args) -> str: - return str([repr(a) for a in args]) - - -def test_argument_dict(a: int = 1, b: int = 2) -> int: - return a + b - - -class JsonRpc: - """ - This class implements JSON RPC for the WebScripts Server. - """ - - functions: Dict[str, Callable] = {"call": test_call} - - @classmethod - def register_function(cls: type, function: Callable, name: str = None): - """ - This function adds a new function in the JSON RPC calls. - """ - - cls.functions[name or function.__name__] = function - - @classmethod - def execute_call(cls: type, json: Dict[str, Any]) -> Dict[str, Any]: - """ - This function performs a JSON RPC call. - """ - - id_ = json.get("id") - params = json.get("params", []) - - print( - isinstance(json, dict), - json.get("jsonrpc") == "2.0", - isinstance(id_, int), - ) - if ( - not isinstance(json, dict) - or json.get("jsonrpc") != "2.0" - or not isinstance(id_, int) - ): - return { - "jsonrpc": "2.0", - "error": {"code": -32600, "message": "Invalid Request"}, - "id": None, - } - - if not isinstance(params, (list, dict)): - return { - "jsonrpc": "2.0", - "error": {"code": -32602, "message": "Invalid params"}, - "id": None, - } - - method_name = json.get("method") - - if method_name not in cls.functions: - return { - "jsonrpc": "2.0", - "error": {"code": -32601, "message": "Method not found"}, - "id": id_, - } - - if isinstance(params, list): - try: - return { - "jsonrpc": "2.0", - "result": cls.functions[method_name](*params), - "id": id_, - } - except Exception: - return { - "jsonrpc": "2.0", - "error": {"code": -32603, "message": "Internal error"}, - "id": id_, - } - else: - try: - return { - "jsonrpc": "2.0", - "result": cls.functions[method_name](**params), - "id": id_, - } - except Exception: - return { - "jsonrpc": "2.0", - "error": {"code": -32603, "message": "Internal error"}, - "id": id_, - } - - def __new__( - cls: type, - environ: _Environ, - user: User, - server: Server, - filename: str, - calls: Union[List[Dict[str, Any]], Dict[str, Any]], - inputs: List[str], - csrf_token: str = None, - ) -> Tuple[str, Dict[str, str], str]: - execute_call = cls.execute_call - if isinstance(calls, list): - if not calls: - return ( - "400 Bad Request", - {"Content-Type": 'application/json; charset="utf-8"'}, - '{"jsonrpc": "2.0", "error": {"code": -32600, ' - '"message": "Invalid Request"}, "id": null}', - ) - - result = [execute_call(call) for call in calls] - elif isinstance(calls, bytes): - return ( - "400 Bad Request", - {"Content-Type": 'application/json; charset="utf-8"'}, - '{"jsonrpc": "2.0", "error": {"code": -32700, ' - '"message": "Parse error"}, "id": null}', - ) - else: - result = execute_call(calls) - - return ( - "200 OK", - {"Content-Type": 'application/json; charset="utf-8"'}, - dumps(result), - ) - - -# https://www.jsonrpc.org/specification - -JsonRpc.register_function(test_argument_list) -JsonRpc.register_function(test_argument_dict, "test_args_dict") diff --git a/modules/cgi.py b/modules/cgi.py deleted file mode 100644 index a0868a8d..00000000 --- a/modules/cgi.py +++ /dev/null @@ -1,164 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -################### -# This tool runs CLI scripts and displays output in a Web Interface. -# Copyright (C) 2021, 2022, 2023 Maurice Lambert - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. - -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. - -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -################### - -""" -This tool runs CLI scripts and displays output in a Web Interface. - -This file implements a CGI server to customize resquests -and responses with non-python script or executable. -""" - -__version__ = "0.0.1" -__author__ = "Maurice Lambert" -__author_email__ = "mauricelambert434@gmail.com" -__maintainer__ = "Maurice Lambert" -__maintainer_email__ = "mauricelambert434@gmail.com" -__description__ = """ -This tool runs CLI scripts and displays output in a Web Interface. - -This file implements a CGI server to customize resquests -and responses with non-python script or executable. -""" -__license__ = "GPL-3.0 License" -__url__ = "https://github.com/mauricelambert/WebScripts" - -copyright = """ -WebScripts Copyright (C) 2021, 2022, 2023 Maurice Lambert -This program comes with ABSOLUTELY NO WARRANTY. -This is free software, and you are welcome to redistribute it -under certain conditions. -""" -license = __license__ -__copyright__ = copyright - -from typing import TypeVar, Dict, List, Tuple -from subprocess import Popen, PIPE # nosec -from os.path import join, dirname -from os import _Environ -from json import dumps -from io import BytesIO - -Server = TypeVar("Server") -User = TypeVar("User") - - -def bin( - environ: _Environ, - user: User, - server: Server, - script_name: str, - arguments: Dict[str, Dict[str, str]], - inputs: List[str], - csrf_token: str = None, -) -> Tuple[str, Dict[str, str], str]: - """ - This function reloads the configuration. - """ - - logs = server.logs - debug = logs.debug - - debug(f"Call CGI function with script: {script_name!r}...") - decoded_query = environ["QUERY_STRING"].replace("+", " ") - - for directory in server.configuration.cgi_path: - script_path = server.research_filename( - join(directory, script_name), no_error=True - ) - if script_path is not None: - break - else: - return "404", None, None - - debug("Build script and get launcher...") - script = server.CommonsClasses.ScriptConfig() - script.dirname = dirname(script_path) - script.path = script_path - script.name = script_name - script.set_defaults() - - script_dict = script.get_dict() - launcher = script.get_Windows_default_script_launcher(script_dict) - - debug(f"Build the command for {script_path!r}...") - if launcher is not None: - command = [launcher, script_path] - else: - command = [script_path] - - if "=" not in decoded_query: - command.append(decoded_query) - - logs.warning(f"CGI script launch with: {command}") - process = Popen( - command, - stdin=PIPE, - stdout=PIPE, - stderr=PIPE, - shell=False, # nosec - env=server.get_environ_strings(environ, user, script), - ) - - debug("Send inputs and get outputs...") - if isinstance(arguments, bytes): - stdout, stderr = process.communicate(arguments) - else: - stdout, stderr = process.communicate(dumps(arguments).encode()) - - stdout = BytesIO(stdout) - - header = True - headers = {} - - debug("Get headers...") - while header: - header = stdout.readline() - - if not header.strip(): - break - - if b":" not in header: - continue - - name, value = header.split(b":", 1) - headers[name.strip().decode("ascii", "ignore")] = value.strip().decode( - "ascii", "ignore" - ) - - debug("Get output...") - if server.debug: - output = stdout.read() + stderr - else: - output = stdout.read() - - stdout.close() - - code = process.returncode - logs.info(f"Script exit code: {code}") - - if code: - return ( - "500 Internal Server Error", - headers, - b"There is an error in the script execution." + output, - ) - - return "200 OK", headers, output diff --git a/modules/csp.py b/modules/csp.py deleted file mode 100644 index e53d839e..00000000 --- a/modules/csp.py +++ /dev/null @@ -1,99 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -################### -# This tool runs CLI scripts and displays output in a Web Interface. -# Copyright (C) 2021, 2022, 2023 Maurice Lambert - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. - -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. - -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -################### - -""" -This tool runs CLI scripts and displays output in a Web Interface. - -This file implements a Content-Security-Policy debug page. -""" - -__version__ = "1.0.0" -__author__ = "Maurice Lambert" -__author_email__ = "mauricelambert434@gmail.com" -__maintainer__ = "Maurice Lambert" -__maintainer_email__ = "mauricelambert434@gmail.com" -__description__ = """ -This tool runs CLI scripts and displays output in a Web Interface. - -This file implements a Content-Security-Policy debug page. -""" -__license__ = "GPL-3.0 License" -__url__ = "https://github.com/mauricelambert/WebScripts" - -copyright = """ -WebScripts Copyright (C) 2021, 2022, 2023 Maurice Lambert -This program comes with ABSOLUTELY NO WARRANTY. -This is free software, and you are welcome to redistribute it -under certain conditions. -""" -license = __license__ -__copyright__ = copyright - -from typing import Tuple, Dict, List, TypeVar -from json.decoder import JSONDecodeError -from error_pages import Request -from json import dumps, loads -from os import _Environ - -global csp_report - -Server = TypeVar("Server") -User = TypeVar("User") - -csp_report = {"report": "No CSP report yet."} - - -def debug( - environ: _Environ, - user: User, - server: Server, - code: str, - arguments: Dict[str, Dict[str, str]], - inputs: List[str], - csrf_token: str = None, -) -> Tuple[str, Dict[str, str], str]: - """ - This function implements a debug page. - """ - - global csp_report - - if isinstance(arguments, bytes): - try: - csp_report = loads(arguments) - except JSONDecodeError: - pass - else: - Request.send_mail( - server.configuration, dumps(csp_report, indent=4) - ) - - return ( - "200 OK", - { - "Content-Security-Policy": ( - "default-src 'self'; form-action 'none'; " - "frame-ancestors 'none'" - ), - "Content-Type": "application/json; charset=utf-8", - }, - dumps(csp_report, indent=4), - ) diff --git a/modules/error_pages.py b/modules/error_pages.py deleted file mode 100644 index 6add5c8f..00000000 --- a/modules/error_pages.py +++ /dev/null @@ -1,443 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -################### -# This tool runs CLI scripts and displays output in a Web Interface. -# Copyright (C) 2021, 2022, 2023 Maurice Lambert - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. - -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. - -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -################### - -""" -This tool runs CLI scripts and displays output in a Web Interface. - -This file implement error, report and request pages by default. -""" - -from typing import Tuple, Dict, List, TypeVar, Any -from csv import reader, writer, QUOTE_ALL -from email.message import EmailMessage -from collections.abc import Iterator -from smtplib import SMTP, SMTP_SSL -from collections import namedtuple -from os import _Environ, path -from threading import Thread -from string import Template -from copy import deepcopy -from html import escape -from sys import modules -from json import dumps -from time import time - -ServerConfiguration = TypeVar("ServerConfiguration") -Server = TypeVar("Server") -User = TypeVar("User") - -_Request = namedtuple( - "_Request", - [ - "ID", - "Time", - "UserName", - "ErrorCode", - "Page", - "UserAgent", - "Subject", - "Reason", - "Name", - ], -) - -__version__ = "1.0.1" -__author__ = "Maurice Lambert" -__author_email__ = "mauricelambert434@gmail.com" -__maintainer__ = "Maurice Lambert" -__maintainer_email__ = "mauricelambert434@gmail.com" -__description__ = """ -This tool runs CLI scripts and displays output in a Web Interface. - -This file implement errors and report/request pages by default. -""" -__license__ = "GPL-3.0 License" -__url__ = "https://github.com/mauricelambert/WebScripts" - -copyright = """ -WebScripts Copyright (C) 2021, 2022, 2023 Maurice Lambert -This program comes with ABSOLUTELY NO WARRANTY. -This is free software, and you are welcome to redistribute it -under certain conditions. -""" -license = __license__ -__copyright__ = copyright - -__all__ = [ - "page_500", - "page_401", - "page_403", - "page_404", - "Request", - "Report", -] - -commons = modules.get("commons") or modules["WebScripts.commons"] -CallableFile = deepcopy(commons.CallableFile) -TokenCSRF = commons.TokenCSRF - -template_script = CallableFile.template_script - -script: str = """ - script_name = "/error_pages/Request/send/${code}"; - script = { - "content_type": "text/plain", - "name": "/error_pages/Request/send/${code}", - "category": "Error ${code}", - "description": "${text}", - "args": [ - { - "input": false, - "name": "title", - "javascript_attributs": {}, - "example": "Access/permissions", - "description": "The reason for your message." - }, - { - "input": false, - "name": "request", - "javascript_attributs": {}, - "example": "I need access to this script.", - "description": "Here your can describe your problem, administrators will read this message." - }, - { - "input": false, - "name": "name", - "javascript_attributs": {}, - "example": "Firstname LASTNAME", - description: "Your name to identify you" - }, - { - "input": false, - "name": "error", - "html_type": "hidden", - "default_value": "${code}", - "javascript_attributs": {} - } - ] - }; - document.title = "Error ${code}"; - script = new Script(script); - script.build_category(); - script.build_card("link_card", "script_cards", "category_content"); // "category" - script.build_card("search_button", "search_result"); -""" - - -def page_500( - environ: _Environ, - user: User, - server: Server, - filename: str, - error: str, -) -> Tuple[str, Dict[str, str], List[bytes]]: - """ - This function uses send_error_page to return the page 500 by default. - """ - - return send_error_page(environ, user, server, filename, error, "500") - - -def page_401( - environ: _Environ, - user: User, - server: Server, - filename: str, - error: str, -) -> Tuple[str, Dict[str, str], List[bytes]]: - """ - This function uses send_error_page to return the page 401 by default. - """ - - return send_error_page(environ, user, server, filename, error, "401") - - -def page_403( - environ: _Environ, - user: User, - server: Server, - filename: str, - error: str, -) -> Tuple[str, Dict[str, str], List[bytes]]: - """ - This function uses send_error_page to return the page 403 by default. - """ - - return send_error_page(environ, user, server, filename, error, "403") - - -def page_404( - environ: _Environ, - user: User, - server: Server, - filename: str, - error: str, -) -> Tuple[str, Dict[str, str], List[bytes]]: - """ - This function uses send_error_page to return the page 404 by default. - """ - - return send_error_page(environ, user, server, filename, error, "404") - - -def send_error_page( - environ: _Environ, - user: User, - server: Server, - filename: str, - error: str, - code: str, - filepath: str = None, -) -> Tuple[str, Dict[str, str], List[bytes]]: - """ - This function returns the default errors responses. - """ - - code = escape(code) - - CallableFile.template_script = template_script.replace( - """script_name="%(name)s";""", - script, - ) - text = escape(filepath or ("Error " + code + " (" + error + ")")) - error_page = CallableFile("script", __file__, text) - _, headers, page = error_page(user, environ["SUB_DIRECTORIES_NUMBER"]) - page = Template(page) - CallableFile.template_script = template_script - - return ( - error, - headers, - [ - page.substitute(code=code, text=text).encode("utf-8"), - ], - ) - - -class Report: - """ - This class implements pages for the report feature by default. - """ - - def new( - environ: _Environ, - user: User, - server: Server, - code: str, - arguments: List[str], - inputs: List[str], - csrf_token: str = None, - ) -> Tuple[str, Dict[str, str], str]: - """ - This function returns the report page by default. - """ - - return send_error_page( - environ, user, server, code, "", code, "Report an error " + code - ) - - -class Request: - """ - This class implements pages for the report feature by default. - """ - - def send_mail( - configuration: ServerConfiguration, - notification: str, - title: str = "[! WebScripts Notification ]", - content_type: str = None, - attachments: List[Tuple[List[Any], Dict[str, Any]]] = [], - ) -> None: - """ - This function send a notification mail. - - Attachments is a list of tuple with args - and kwargs of EmailMessage.add_attachment function. - """ - - server_name = getattr(configuration, "smtp_server", None) - starttls = getattr(configuration, "smtp_starttls", None) - password = getattr(configuration, "smtp_password", None) - - if not server_name: - return None - - try: - if starttls: - server = SMTP( - server_name, getattr(configuration, "smtp_port", 587) - ) - elif getattr(configuration, "smtp_ssl", None): - server = SMTP_SSL( - server_name, getattr(configuration, "smtp_port", 465) - ) - else: - server = SMTP( - server_name, getattr(configuration, "smtp_port", 25) - ) - except TimeoutError: - return None - - if password: - server.login(configuration.email, password) - - email = EmailMessage() - email.set_content(notification) - email["To"] = ", ".join(configuration.admin_adresses) - email["From"] = configuration.notification_address - email["Subject"] = title - - if content_type is not None: - email.replace_header("Content-Type", content_type) - - add_attachment = email.add_attachment - for attachment_args, attachment_kwargs in attachments: - add_attachment( - *attachment_args, - **attachment_kwargs, - ) - - server.send_message(email) - server.quit() - - def save( - username: str, - code: str, - url: str, - user_agent: str, - subject: str, - name: str, - reason: str, - ) -> None: - """ - This function save the report/request to a CSV file. - """ - - filename = path.join( - path.dirname(__file__), "..", "data", "requests.csv" - ) - - def get_requests() -> Iterator[_Request]: - """ - This function build Request from database. - """ - - yield from map( - _Request._make, - reader( - open(filename, "r", newline=""), - quoting=QUOTE_ALL, - ), - ) - - id_ = 0 - first = True - for request in get_requests(): - if first: # columns - first = False - continue - id_ = int(request.ID) + 1 - - with open(filename, "a", newline="") as file: - csvfile = writer(file, quoting=QUOTE_ALL) - csvfile.writerow( - [ - str(id_), - str(time()), - username, - code, - url, - user_agent, - subject, - name, - reason, - ] - ) - - def send( - environ: _Environ, - user: User, - server: Server, - code: str, - arguments: List[str], - inputs: List[str], - csrf_token: str = None, - ) -> Tuple[str, Dict[str, str], str]: - """ - This function save and send request or report. - """ - - referer = escape(environ.get("HTTP_REFERER", "")) - user_agent = escape(environ.get("HTTP_USER_AGENT", "")) - subject = escape(arguments[0]) - name = escape(arguments[1]) - reason = escape(arguments[2]) - code = escape(code) - user.name = escape(user.name) - - for string in ( - referer, - user_agent, - code, - user.name, - subject, - name, - reason, - ): - if not string.isprintable(): - raise ValueError( - f"Strings must be printable: '{string}' is not." - ) - - notification = ( - f'The user named: "{user.name}" get a HTTP error ' - f'code {code} on "{referer}" using "{user_agent}".' - f'\nRequest or report from "{name}": \n\t' - f"Subject: {subject} \n\tReason: {reason}" - ) - - Request.save( - user.name, code, referer, user_agent, subject, name, reason - ) - Thread( - target=Request.send_mail, - args=( - server.configuration, - notification, - ), - ).start() - - return ( - "200 OK", - {"Content-Type": "application/json; charset=utf-8"}, - dumps( - { - "stdout": "Request or report sent successfully.", - "stderr": "", - "code": 0, - "Content-Type": "text/plain", - "Stderr-Content-Type": "text/plain", - "error": "No errors", - "csrf": TokenCSRF.build_token(user), - } - ), - ) diff --git a/modules/notification.py b/modules/notification.py deleted file mode 100644 index 567d756a..00000000 --- a/modules/notification.py +++ /dev/null @@ -1,152 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -################### -# This tool runs CLI scripts and displays output in a Web Interface. -# Copyright (C) 2021, 2022, 2023 Maurice Lambert - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. - -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. - -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -################### - -""" -This tool runs CLI scripts and displays output in a Web Interface. - -This module adds notifications on WebScripts pages. -""" - -__version__ = "0.0.1" -__author__ = "Maurice Lambert" -__author_email__ = "mauricelambert434@gmail.com" -__maintainer__ = "Maurice Lambert" -__maintainer_email__ = "mauricelambert434@gmail.com" -__description__ = """ -This tool runs CLI scripts and displays output in a Web Interface. - -This module adds notifications on WebScripts pages. -""" -__license__ = "GPL-3.0 License" -__url__ = "https://github.com/mauricelambert/WebScripts" - -copyright = """ -WebScripts Copyright (C) 2021, 2022, 2023 Maurice Lambert -This program comes with ABSOLUTELY NO WARRANTY. -This is free software, and you are welcome to redistribute it -under certain conditions. -""" -license = __license__ -__copyright__ = copyright - -__all__ = ["add"] - -from typing import TypeVar, List, Tuple, Dict -from urllib.parse import parse_qs -from types import MethodType -from html import escape -from os import _Environ -from sys import modules - -Server = TypeVar("Server") -User = TypeVar("User") - -notification_id: int = 0 - -module_getter: MethodType = modules.get -check_right = module_getter( - ( - "WebScripts.Pages" - if module_getter("WebScripts") - else "WebScripts38.Pages" - ), - module_getter("Pages"), -).check_right -commons = module_getter("commons") or modules["WebScripts.commons"] -TokenCSRF = commons.TokenCSRF - - -def add( - environ: _Environ, - user: User, - server: Server, - filename: str, - arguments: bytes, - inputs: List[str], - csrf_token: str = None, -) -> Tuple[str, Dict[str, str], str]: - """ - This function adds a new notification - in WebScripts pages. - """ - - global notification_id - script = server.pages.scripts.get("change_user_password.py") - permissions = getattr(server.configuration, "admin_groups", None) - - if script and not check_right(user, script): - return "403", {}, b"" - elif permissions and not any(g in permissions for g in user["groups"]): - return "403", {}, b"" - - if arguments: - form = parse_qs(arguments.decode()) - environ_getter = environ.get - text = form.get("text") - if text and TokenCSRF.check_csrf( - user, - form.get("csrf", [None])[0], - getattr(server.configuration, "csrf_max_time", 300), - environ_getter("HTTP_REFERER"), - server.get_baseurl(environ_getter, environ), - ): - server.CommonsClasses.CallableFile.template_header += f""" -
- -

- {escape(user.name)}:\ - - {escape(text[0])} -

-
- """ - notification_id += 1 - else: - return ( - "200 OK", - { - "Content-Security-Policy": server.headers[ - "Content-Security-Policy" - ].replace("form-action 'none';", "form-action 'self';") - }, - f""" - - - - -
- - - -
- - - """, - ) - - return ( - "200 OK", - {"Content-Type": "text/plain"}, - "OK: notification is added.", - ) diff --git a/modules/rss.py b/modules/rss.py deleted file mode 100644 index 34f80164..00000000 --- a/modules/rss.py +++ /dev/null @@ -1,273 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -################### -# This tool runs CLI scripts and displays output in a Web Interface. -# Copyright (C) 2021, 2022, 2023 Maurice Lambert - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. - -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. - -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -################### - -""" -This tool runs CLI scripts and displays output in a Web Interface. - -This file implements a RSS feed. -""" - -__version__ = "0.0.1" -__author__ = "Maurice Lambert" -__author_email__ = "mauricelambert434@gmail.com" -__maintainer__ = "Maurice Lambert" -__maintainer_email__ = "mauricelambert434@gmail.com" -__description__ = """ -This tool runs CLI scripts and displays output in a Web Interface. - -This file implements a RSS feed. -""" -__license__ = "GPL-3.0 License" -__url__ = "https://github.com/mauricelambert/WebScripts" - -copyright = """ -WebScripts Copyright (C) 2021, 2022, 2023 Maurice Lambert -This program comes with ABSOLUTELY NO WARRANTY. -This is free software, and you are welcome to redistribute it -under certain conditions. -""" -license = __license__ -__copyright__ = copyright - -from typing import Tuple, Dict, List, TypeVar, Iterable -from csv import writer, DictReader, QUOTE_ALL -from time import strftime, localtime -from os.path import join -from os import _Environ -from io import StringIO -from json import dumps -from enum import Enum - -Server = TypeVar("Server") -User = TypeVar("User") - - -class FIELDS(Enum): - guid = 0 - author = 1 - title = 2 - description = 3 - link = 4 - categories = 5 - pubDate = 6 - comments = 7 - - -def get_rss_path(server: Server) -> str: - """ - This function returns the rss path. - """ - - return join(server.configuration.data_dir, "rss.csv") - - -class Feed: - """ - This class implements the RSS feed. - """ - - rss_path: str = None - - required: List[str] = ["title", "description", "link", "categories"] - optional: List[str] = ["comments"] - default: List[str] = ["author", "guid", "pubDate", "lastBuildDate"] - - @classmethod - def csv( - cls: type, - environ: _Environ, - user: User, - server: Server, - category: str, - arguments: Dict[str, Dict[str, str]], - inputs: List[str], - csrf_token: str = None, - ) -> Tuple[str, Dict[str, str], str]: - """ - This function prints the RSS feed content as CSV. - """ - - rss_path = cls.rss_path = cls.rss_path or get_rss_path(server) - - if not category: - with open(rss_path, "r") as file: - data = file.read() - - return ( - "200 OK", - {"Content-Type": "text/csv"}, - data, - ) - - data = StringIO() - writerow = writer(data, quoting=QUOTE_ALL).writerow - - with open( - join(server.configuration.data_dir, "rss.csv"), "r", newline="" - ) as csvfile: - writerow( - [ - "guid", - "author", - "title", - "description", - "link", - "categories", - "pubDate", - "comments", - ] - ) - [ - writerow(row.values()) - for row in DictReader(csvfile) - if category in row["categories"].split(",") - ] - - data.seek(0) - - return ( - "200 OK", - {"Content-Type": "text/csv; charset=utf-8"}, - data.read(), - ) - - @classmethod - def json( - cls: type, - environ: _Environ, - user: User, - server: Server, - category: str, - arguments: Dict[str, Dict[str, str]], - inputs: List[str], - csrf_token: str = None, - ) -> Tuple[str, Dict[str, str], str]: - """ - This function prints the RSS feed content as JSON. - """ - - rss_path = cls.rss_path = cls.rss_path or get_rss_path(server) - - with open(rss_path, "r", newline="") as file: - data = dumps( - [ - row - for row in DictReader(file) - if not category or category in row["categories"].split(",") - ] - ) - - return ( - "200 OK", - {"Content-Type": "application/json; charset=utf-8"}, - data, - ) - - def __new__( - cls: type, - environ: _Environ, - user: User, - server: Server, - category: str, - arguments: Dict[str, Dict[str, str]], - inputs: List[str], - csrf_token: str = None, - ) -> Tuple[str, Dict[str, str], Iterable[bytes]]: - """ - This function prints the RSS feed. - """ - - full_url = server.get_fullurl(environ) - base_url = server.get_baseurl(environ.get, environ) - rss_path = cls.rss_path = cls.rss_path or get_rss_path(server) - - data = cls.generator(full_url, base_url, rss_path, category) - - return ( - "200 OK", - {"Content-Type": "application/xml; charset=utf-8"}, - data, - ) - - @classmethod - def generator( - cls: type, full_url: str, base_url: str, rss_path: str, category: str - ) -> Iterable[bytes]: - """ - This generator returns RSS feed content. - """ - - def get_date(timestamp: str) -> str: - return strftime("%a, %d %b %Y %X %z", localtime(float(timestamp))) - - file = open(rss_path, "r", newline="") - csvreader = DictReader(file) - - for item in csvreader: - pass - - last_time = get_date(item["pubDate"]) - - file.seek(0) - csvreader = DictReader(file) - - yield f""" - - - WebScripts - - {base_url} - - RSS feed of WebScripts Server to - manage systems, accounts and networks. - - {last_time} - en-US - hourly - 1""".encode() - - for item in csvreader: - categories = item.pop("categories") - if not category or category in categories: - yield ( - f""" - - %(title)s - %(author)s - %(comments)s - %(link)s - {get_date(item.pop("pubDate"))} - %(guid)s - %(description)s - { - ''.join(f'{category}' - for category in categories.split(',')) - } - - """ - % item - ).encode() - - yield """ - -""".encode() - - file.close() diff --git a/modules/share.py b/modules/share.py deleted file mode 100644 index aa07d9ac..00000000 --- a/modules/share.py +++ /dev/null @@ -1,259 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -################### -# This tool runs CLI scripts and displays output in a Web Interface. -# Copyright (C) 2021, 2022, 2023 Maurice Lambert - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. - -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. - -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -################### - -""" -This tool runs CLI scripts and displays output in a Web Interface. - -This file download and upload functions for scripts, -tools and command line client. -""" - -__version__ = "1.0.0" -__author__ = "Maurice Lambert" -__author_email__ = "mauricelambert434@gmail.com" -__maintainer__ = "Maurice Lambert" -__maintainer_email__ = "mauricelambert434@gmail.com" -__description__ = """ -This tool runs CLI scripts and displays output in a Web Interface. - -This file download and upload functions for scripts, -tools and command line client. -""" -__license__ = "GPL-3.0 License" -__url__ = "https://github.com/mauricelambert/WebScripts" - -copyright = """ -WebScripts Copyright (C) 2021, 2022, 2023 Maurice Lambert -This program comes with ABSOLUTELY NO WARRANTY. -This is free software, and you are welcome to redistribute it -under certain conditions. -""" -license = __license__ -__copyright__ = copyright - -__all__ = ["Download", "upload"] - -from typing import List, Tuple, Dict, TypeVar, Union, Iterable -from os import path, _Environ, environ as env -from sys import path as syspath, modules -from tempfile import TemporaryFile -from lzma import open as lzmaopen -from gzip import open as gzipopen -from types import MethodType -from json import dumps - -# if "WebScripts" in sys.modules: -# base_path = [path.dirname(sys.modules["WebScripts"].__file__)] -# elif "WebScripts38" in sys.modules: -# base_path = [path.dirname(sys.modules["WebScripts38"].__file__)] -# else: -# base_path = [path.dirname(__file__), ".."] - -syspath.insert( - 0, - path.join(env.get("WEBSCRIPTS_PATH", ""), "scripts", "uploads", "modules"), -) - -from uploads_management import ( - User, - write_file, - check_permissions, - get_real_file_name, - get_file, - get_files, - Upload, -) - -syspath.pop(0) -Server = TypeVar("Server") - -module_getter: MethodType = modules.get -check_right = module_getter( - ( - "WebScripts.Pages" - if module_getter("WebScripts") - else "WebScripts38.Pages" - ), - module_getter("Pages"), -).check_right - - -class Download: - """ - This class implement download functions for filename or ID. - """ - - def filename( - environ: _Environ, - user: User, - server: Server, - filename: str, - arguments: List[str], - inputs: List[str], - csrf_token: str = None, - ) -> Tuple[str, Dict[str, str], Union[Iterable[bytes], bytes]]: - """ - This funtion download file by filename. - """ - - uploads, counter = get_file(filename) - - if len(uploads) == 0: - return "404", {}, b"" - - file = uploads[-1] - - try: - check_permissions(file, user.get_dict(), "read") - except PermissionError: - return "403", {}, b"" - except FileNotFoundError: - return "404", {}, b"" - - headers = { - "Content-Type": "application/octet-stream", - } - - if not file.no_compression: - headers["Content-Encoding"] = "gzip" - - return ( - "200 OK", - headers, - Download.get_data(file), - ) - - @staticmethod - def get_data(file: Upload) -> Iterable[bytes]: - """ - This function get file and manage the compression. - """ - - file_ = open( - get_real_file_name(file.name, float(file.timestamp)), "rb" - ) - - if file.is_binary == "binary" and not file.no_compression: - tempfile = TemporaryFile() - gzipfile = gzipopen(tempfile, "wb") - writer = gzipfile.write - [writer(line) for line in lzmaopen(file_)] - tempfile.seek(0) - gzipfile = gzipopen(tempfile) - data = gzipfile - else: - data = file_ - - return data - - def id( - environ: _Environ, - user: User, - server: Server, - id_: str, - arguments: List[str], - inputs: List[str], - csrf_token: str = None, - ) -> Tuple[str, Dict[str, str], Union[Iterable[bytes], bytes]]: - """ - This funtion download file by ID. - """ - - script = server.pages.scripts.get("get_any_file.py") - permissions = getattr(server.configuration, "admin_groups", None) - - if script and not check_right(user, script): - return "403", {}, b"" - elif permissions and not any(g in permissions for g in user["groups"]): - return "403", {}, b"" - - headers = { - "Content-Type": "application/octet-stream", - } - - ask_file = None - - for file in get_files(): - if file.ID == id_: - ask_file = file - break - - if ask_file is None: - return "404", {}, b"" - - if not ask_file.no_compression: - headers["Content-Encoding"] = "gzip" - - return ( - "200 OK", - headers, - Download.get_data(ask_file), - ) - - -def upload( - environ: _Environ, - user: User, - server: Server, - filename: str, - data: bytes, - none: None, - csrf_token: str = None, -) -> Tuple[str, Dict[str, str], str]: - """ - This funtion upload file. - """ - - if not isinstance(data, bytes): - data = dumps(data) - else: - data = data.decode("latin-1") - - permissions = max(user.groups) - - read = int(environ.get("HTTP_READ_PERMISSION", permissions)) - write = int(environ.get("HTTP_WRITE_PERMISSION", permissions)) - delete = int(environ.get("HTTP_DELETE_PERMISSION", permissions)) - - hidden = environ.get("HTTP_HIDDEN", False) - binary = environ.get("HTTP_BINARY", False) - no_compression = environ.get("HTTP_NO_COMPRESSION", False) - is_b64 = environ.get("HTTP_IS_BASE64", False) - - env["USER"] = dumps(user.get_dict()) - - write_file( - data, - filename, - read, - write, - delete, - hidden, - binary, - no_compression, - is_b64, - ) - - return ( - "200 OK", - {"Content-Type": "text/plain"}, - "OK: file is uploaded.", - ) diff --git a/scripts/account/add_group.py b/scripts/account/add_group.py deleted file mode 100644 index 0b9972d8..00000000 --- a/scripts/account/add_group.py +++ /dev/null @@ -1,78 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -################### -# This file adds a new group -# Copyright (C) 2021, 2022, 2023 Maurice Lambert - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. - -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. - -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -################### - -""" -This tool runs CLI scripts and displays output in a Web Interface. - -This file adds a new group. -""" - -__version__ = "0.0.2" -__author__ = "Maurice Lambert" -__author_email__ = "mauricelambert434@gmail.com" -__maintainer__ = "Maurice Lambert" -__maintainer_email__ = "mauricelambert434@gmail.com" -__description__ = """ -This tool runs CLI scripts and displays output in a Web Interface. - -This file adds a new group. -""" -__license__ = "GPL-3.0 License" -__url__ = "https://github.com/mauricelambert/WebScripts" - -copyright = """ -WebScripts Copyright (C) 2021, 2022, 2023 Maurice Lambert -This program comes with ABSOLUTELY NO WARRANTY. -This is free software, and you are welcome to redistribute it -under certain conditions. -""" -license = __license__ -__copyright__ = copyright - -__all__ = [] - -from modules.manage_defaults_databases import add_group, GroupError -from sys import exit, argv, stderr - - -def main() -> int: - if len(argv) != 3 and not argv[2].isdigit(): - print( - "USAGES: add_group.py [NAME string required] [ID integer required]" - ) - return 1 - - try: - group = add_group(argv[2], argv[1]) - except GroupError as error: - print(error.__class__.__name__, error, file=stderr) - return 2 - except Exception as error: - print(error.__class__.__name__, error, file=stderr) - return 127 - - print(f"Group added:\n\t - Name: {group.name}\n\t - ID: {group.ID}") - - return 0 - - -if __name__ == "__main__": - exit(main()) diff --git a/scripts/account/add_user.py b/scripts/account/add_user.py deleted file mode 100644 index 3952fa6c..00000000 --- a/scripts/account/add_user.py +++ /dev/null @@ -1,174 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -################### -# This file adds a new user. -# Copyright (C) 2021, 2022, 2023 Maurice Lambert - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. - -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. - -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -################### - -""" -This tool runs CLI scripts and displays output in a Web Interface. - -This file adds a new user. -""" - -__version__ = "1.0.3" -__author__ = "Maurice Lambert" -__author_email__ = "mauricelambert434@gmail.com" -__maintainer__ = "Maurice Lambert" -__maintainer_email__ = "mauricelambert434@gmail.com" -__description__ = """ -This tool runs CLI scripts and displays output in a Web Interface. - -This file adds a new user. -""" -__license__ = "GPL-3.0 License" -__url__ = "https://github.com/mauricelambert/WebScripts" - -copyright = """ -WebScripts Copyright (C) 2021, 2022, 2023 Maurice Lambert -This program comes with ABSOLUTELY NO WARRANTY. -This is free software, and you are welcome to redistribute it -under certain conditions. -""" -license = __license__ -__copyright__ = copyright - -__all__ = ["parse_args", "main"] - -from modules.manage_defaults_databases import ( - add_user, - UserError, - get_dict_groups, -) -from argparse import ArgumentParser, Namespace -from sys import exit, stderr - - -def parse_args() -> Namespace: - """ - This function parse command line arguments. - """ - - parser = ArgumentParser(description="This file adds a new user.") - add_argument = parser.add_argument - - add_argument("username", help="Name of the new user") - add_argument("password", help="Password of the new user") - add_argument( - "--groups", - "-g", - help="List of groups IDs to add permissions to the new user.", - type=int, - nargs="+", - default=[], - ) - add_argument( - "--group-names", - "-n", - help="List of groups names to add permissions to the new user.", - nargs="+", - default=[], - ) - add_argument( - "--ips", - "-i", - help="List of glob syntax for authorized IPs", - type=str, - nargs="+", - default=["*"], - ) - add_argument( - "--categories", - "-c", - help="List of glob syntax for authorized categories", - type=str, - nargs="+", - default=["*"], - ) - add_argument( - "--scripts", - "-s", - help="List of glob syntax for authorized scripts", - type=str, - nargs="+", - default=["*"], - ) - return parser.parse_args() - - -def main() -> int: - """ - This function adds a new user using the - default user manager. - """ - - arguments = parse_args() - - groups = { - k.casefold(): v for k, v in get_dict_groups(by_name=True).items() - } - user_namedgroups = [] - - for name in arguments.group_names: - name = name.casefold() - if name in groups: - user_namedgroups.append(groups[name]) - else: - print("Group name not found:", name, file=stderr) - return 4 - - groups = arguments.groups + user_namedgroups - if not groups: - print( - "A group is required you must use [--groups/-g] or/and " - "[--group-names/-n] option.", - file=stderr, - ) - return 3 - - try: - user = add_user( - arguments.username, - arguments.password, - groups, - arguments.ips, - arguments.categories, - arguments.scripts, - ) - except UserError as error: - print(error, file=stderr) - return 2 - except Exception as error: - print(error, file=stderr) - return 127 - - groups = get_dict_groups() - - print( - f"User added:\n\t - Name: {user.name!r}\n\t - ID: {user.ID}\n\t - IPs:" - f" {user.IPs}\n\t - Groups: " - + ",".join( - f'{groups.get(group, "UNKNOWN")!r} ({group})' - for group in user.groups.split(",") - ) - ) - - return 0 - - -if __name__ == "__main__": - exit(main()) diff --git a/scripts/account/api_view_groups.py b/scripts/account/api_view_groups.py deleted file mode 100644 index fb920a45..00000000 --- a/scripts/account/api_view_groups.py +++ /dev/null @@ -1,110 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -################### -# This file print groups in JSON objects -# Copyright (C) 2021, 2022, 2023 Maurice Lambert - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. - -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. - -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -################### - -""" -This tool runs CLI scripts and displays output in a Web Interface. - -This file can print groups in JSON objects. -""" - -__version__ = "0.1.0" -__author__ = "Maurice Lambert" -__author_email__ = "mauricelambert434@gmail.com" -__maintainer__ = "Maurice Lambert" -__maintainer_email__ = "mauricelambert434@gmail.com" -__description__ = """ -This tool runs CLI scripts and displays output in a Web Interface. - -This file can print groups in JSON objects.""" -__license__ = "GPL-3.0 License" -__url__ = "https://github.com/mauricelambert/WebScripts" - -copyright = """ -WebScripts Copyright (C) 2021, 2022, 2023 Maurice Lambert -This program comes with ABSOLUTELY NO WARRANTY. -This is free software, and you are welcome to redistribute it -under certain conditions. -""" -license = __license__ -__copyright__ = copyright - -__all__ = [] - -from modules.manage_defaults_databases import get_groups -from argparse import ArgumentParser, Namespace -from sys import exit, stdout, stderr -from json import dump - - -def parse_args() -> Namespace: - """ - This function parse command line arguments. - """ - - parser = ArgumentParser() - parser.add_argument( - "--ids", - "-i", - help="List of group IDs to display them only.", - nargs="+", - default=[], - ) - parser.add_argument( - "--names", - "-n", - help="List of group names to display them only.", - nargs="+", - default=[], - ) - return parser.parse_args() - - -def main() -> int: - """ - Main function to print users using default manager for group database. - """ - - arguments = parse_args() - - for i, value in enumerate(arguments.ids): - if not value.isdigit(): - print( - f'ERROR: ids must be integer. "{value}" is not digits.', - file=stderr, - ) - return 3 - - groups = [] - - for group in get_groups(): - if ( - (len(arguments.ids) == 0 and len(arguments.names) == 0) - or (arguments.ids and group.ID in arguments.ids) - or (arguments.names and group.name in arguments.names) - ): - groups.append(group._asdict()) - - dump(groups, stdout) - return 0 - - -if __name__ == "__main__": - exit(main()) diff --git a/scripts/account/api_view_users.py b/scripts/account/api_view_users.py deleted file mode 100644 index ee1873c0..00000000 --- a/scripts/account/api_view_users.py +++ /dev/null @@ -1,111 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -################### -# This file prints users in JSON objects -# Copyright (C) 2021, 2022, 2023 Maurice Lambert - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. - -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. - -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -################### - -""" -This tool runs CLI scripts and displays output in a Web Interface. - -This file prints users in JSON objects. -""" - -__version__ = "0.1.0" -__author__ = "Maurice Lambert" -__author_email__ = "mauricelambert434@gmail.com" -__maintainer__ = "Maurice Lambert" -__maintainer_email__ = "mauricelambert434@gmail.com" -__description__ = """ -This tool runs CLI scripts and displays output in a Web Interface. - -This file prints users in JSON objects. -""" -__license__ = "GPL-3.0 License" -__url__ = "https://github.com/mauricelambert/WebScripts" - -copyright = """ -WebScripts Copyright (C) 2021, 2022, 2023 Maurice Lambert -This program comes with ABSOLUTELY NO WARRANTY. -This is free software, and you are welcome to redistribute it -under certain conditions. -""" -license = __license__ -__copyright__ = copyright - -__all__ = [] - -from modules.manage_defaults_databases import get_users -from argparse import ArgumentParser, Namespace -from sys import exit, stdout, stderr -from json import dump - - -def parse_args() -> Namespace: - """ - This function parse command line arguments. - """ - - parser = ArgumentParser() - parser.add_argument( - "--ids", - "-i", - help="List of user IDs to display them only.", - nargs="+", - default=[], - ) - parser.add_argument( - "--names", - "-n", - help="List of user names to display them only.", - nargs="+", - default=[], - ) - return parser.parse_args() - - -def main() -> int: - """ - Main function to print users using default manager for user database. - """ - - arguments = parse_args() - - for i, value in enumerate(arguments.ids): - if not value.isdigit(): - print( - f'ERROR: ids must be integer. "{value}" is not digits.', - file=stderr, - ) - return 3 - - users = [] - - for user in get_users(): - if ( - (len(arguments.ids) == 0 and len(arguments.names) == 0) - or (arguments.ids and user.ID in arguments.ids) - or (arguments.names and user.name in arguments.names) - ): - users.append(user._asdict()) - - dump(users, stdout) - return 0 - - -if __name__ == "__main__": - exit(main()) diff --git a/scripts/account/auth.py b/scripts/account/auth.py deleted file mode 100644 index cb8ab650..00000000 --- a/scripts/account/auth.py +++ /dev/null @@ -1,115 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -################### -# This file authenticates users -# Copyright (C) 2021, 2022, 2023 Maurice Lambert - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. - -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. - -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -################### - -""" -This tool runs CLI scripts and displays output in a Web Interface. - -This file authenticates users -""" - -__version__ = "1.0.0" -__author__ = "Maurice Lambert" -__author_email__ = "mauricelambert434@gmail.com" -__maintainer__ = "Maurice Lambert" -__maintainer_email__ = "mauricelambert434@gmail.com" -__description__ = """ -This tool runs CLI scripts and displays output in a Web Interface. - -This file authenticates users -""" -__license__ = "GPL-3.0 License" -__url__ = "https://github.com/mauricelambert/WebScripts" - -copyright = """ -WebScripts Copyright (C) 2021, 2022, 2023 Maurice Lambert -This program comes with ABSOLUTELY NO WARRANTY. -This is free software, and you are welcome to redistribute it -under certain conditions. -""" -license = __license__ -__copyright__ = copyright - -__all__ = ["parse_args", "main"] - -from modules.manage_defaults_databases import auth -from argparse import ArgumentParser, Namespace -from sys import stderr, exit -from os import environ -from json import dumps - - -def parse_args() -> Namespace: - """ - This function parses command line arguments. - """ - - parser = ArgumentParser("This script authenticates users.") - add_argument = parser.add_argument - add_argument("--username", "-u", help="Username to authenticate the user.") - add_argument("--password", "-p", help="Password to authenticate the user.") - add_argument("--api-key", "-a", help="API key to authenticate the user.") - return parser.parse_args() - - -def main() -> None: - """ - This function authenticates a user. - """ - - arguments = parse_args() - - if ( - arguments.username is None or arguments.password is None - ) and arguments.api_key is None: - print( - "USAGES:\n\t - auth.py --username [USERNAME string required]" - " --password [PASSWORD string required]\n\t - auth.py --api-key" - " [APIKEY string required]", - file=stderr, - ) - return 1 - - try: - user = auth(**arguments.__dict__) - except Exception as error: - print(error, file=stderr) - return 127 - - if user is None: - print("Authentication failed.") - return 3 - - print( - dumps( - { - "id": user.ID, - "name": user.name, - "ip": environ["REMOTE_IP"], - "groups": user.groups, - "categories": user.categories, - "scripts": user.scripts, - } - ) - ) - - -if __name__ == "__main__": - exit(main()) diff --git a/scripts/account/change_my_password.py b/scripts/account/change_my_password.py deleted file mode 100644 index 484b2538..00000000 --- a/scripts/account/change_my_password.py +++ /dev/null @@ -1,105 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -################### -# This file can change the password of current user -# Copyright (C) 2021, 2022, 2023 Maurice Lambert - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. - -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. - -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -################### - -""" -This tool runs CLI scripts and displays output in a Web Interface. - -This file can change the password of current user. -""" - -__version__ = "0.1.0" -__author__ = "Maurice Lambert" -__author_email__ = "mauricelambert434@gmail.com" -__maintainer__ = "Maurice Lambert" -__maintainer_email__ = "mauricelambert434@gmail.com" -__description__ = """ -This tool runs CLI scripts and displays output in a Web Interface. - -This file can change the password of current user.""" -__license__ = "GPL-3.0 License" -__url__ = "https://github.com/mauricelambert/WebScripts" - -copyright = """ -WebScripts Copyright (C) 2021, 2022, 2023 Maurice Lambert -This program comes with ABSOLUTELY NO WARRANTY. -This is free software, and you are welcome to redistribute it -under certain conditions. -""" -license = __license__ -__copyright__ = copyright - -__all__ = [] - -from modules.manage_defaults_databases import change_user_password -from argparse import ArgumentParser, Namespace -from sys import stderr, exit -from os import environ -from json import loads - - -def parse_args() -> Namespace: - """This function parse command line arguments.""" - - parser = ArgumentParser( - description="This script change your WebScripts password." - ) - parser_add_argument = parser.add_argument - parser_add_argument("old_password", help="Your current password") - parser_add_argument("password", help="New password") - parser_add_argument( - "password_confirmation", help="New password configuration" - ) - return parser.parse_args() - - -def main() -> int: - """Main function to change your password.""" - - arguments = parse_args() - user_id = str(loads(environ["USER"])["id"]) - - if arguments.password != arguments.password_confirmation: - print("Password and password confirmation do not match.", file=stderr) - return 3 - - try: - user = change_user_password( - user_id, arguments.password, old_password=arguments.old_password - ) - except Exception as error: - print(error) - return 127 - - if user is None: - print(f"User ID: {user_id} doesn't exist.", file=stderr) - return 2 - elif user is False: - print("Authentication failed: Old password is not valid.", file=stderr) - return 3 - - print( - f"Password changed for user:\n\t - Name: {user.name}\n\t - ID:" - f" {user.ID}\n\t - IPs: {user.IPs}\n\t - Groups: {user.groups}" - ) - - -if __name__ == "__main__": - exit(main()) diff --git a/scripts/account/change_user_password.py b/scripts/account/change_user_password.py deleted file mode 100644 index 1182c78f..00000000 --- a/scripts/account/change_user_password.py +++ /dev/null @@ -1,86 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -################### -# This file can change a user password -# Copyright (C) 2021, 2022, 2023 Maurice Lambert - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. - -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. - -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -################### - -"""# This tool runs CLI scripts and displays output in a Web Interface. - -This file can change a user password.""" - -__version__ = "0.0.1" -__author__ = "Maurice Lambert" -__author_email__ = "mauricelambert434@gmail.com" -__maintainer__ = "Maurice Lambert" -__maintainer_email__ = "mauricelambert434@gmail.com" -__description__ = """ -This tool runs CLI scripts and displays output in a Web Interface. - -This file can change a user password.""" -__license__ = "GPL-3.0 License" -__url__ = "https://github.com/mauricelambert/WebScripts" - -copyright = """ -WebScripts Copyright (C) 2021, 2022, 2023 Maurice Lambert -This program comes with ABSOLUTELY NO WARRANTY. -This is free software, and you are welcome to redistribute it -under certain conditions. -""" -license = __license__ -__copyright__ = copyright - -__all__ = [] - -from modules.manage_defaults_databases import change_user_password -from argparse import ArgumentParser, Namespace -import sys - - -def parse_args() -> Namespace: - """This function parse command line arguments.""" - - parser = ArgumentParser() - parser.add_argument("user_id", help="User ID") - parser.add_argument("password", help="New password") - return parser.parse_args() - - -def main() -> None: - """Main function to change a user password.""" - - arguments = parse_args() - - try: - user = change_user_password(arguments.user_id, arguments.password) - except Exception as error: - print(error) - sys.exit(127) - - if user is None: - print(f"User ID: {arguments.user_id} doesn't exist.") - sys.exit(2) - - print( - f"Password changed for user:\n\t - Name: {user.name}\n\t - ID: " - f"{user.ID}\n\t - IPs: {user.IPs}\n\t - Groups: {user.groups}" - ) - - -if __name__ == "__main__": - main() - sys.exit(0) diff --git a/scripts/account/delete_group.py b/scripts/account/delete_group.py deleted file mode 100644 index 481903aa..00000000 --- a/scripts/account/delete_group.py +++ /dev/null @@ -1,73 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -################### -# This file can delete group and print the deleted group -# Copyright (C) 2021, 2022, 2023 Maurice Lambert - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. - -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. - -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -################### - -"""# This tool runs CLI scripts and displays output in a Web Interface. - -This file can delete group and print the deleted group.""" - -__version__ = "0.0.1" -__author__ = "Maurice Lambert" -__author_email__ = "mauricelambert434@gmail.com" -__maintainer__ = "Maurice Lambert" -__maintainer_email__ = "mauricelambert434@gmail.com" -__description__ = """ -This tool runs CLI scripts and displays output in a Web Interface. - -This file can delete group and print the deleted group.""" -__license__ = "GPL-3.0 License" -__url__ = "https://github.com/mauricelambert/WebScripts" - -copyright = """ -WebScripts Copyright (C) 2021, 2022, 2023 Maurice Lambert -This program comes with ABSOLUTELY NO WARRANTY. -This is free software, and you are welcome to redistribute it -under certain conditions. -""" -license = __license__ -__copyright__ = copyright - -__all__ = [] - -from modules.manage_defaults_databases import delete_group -import sys - - -def main() -> None: - if len(sys.argv) != 2 and sys.argv[1].isdigit(): - try: - group = delete_group(int(sys.argv[1])) - except Exception as error: - print(error) - sys.exit(127) - - if group is None: - print(f"Group ID: {sys.argv[1]} doesn't exist.") - sys.exit(2) - - print(f"Deleted group:\n\t - Name: {group.name}\n\t - ID: {group.ID}") - else: - print("USAGE: delete_group.py [ID integer required]") - sys.exit(1) - - -if __name__ == "__main__": - main() - sys.exit(0) diff --git a/scripts/account/delete_user.py b/scripts/account/delete_user.py deleted file mode 100644 index cf647f66..00000000 --- a/scripts/account/delete_user.py +++ /dev/null @@ -1,72 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -################### -# This file can delete user and print the deleted user -# Copyright (C) 2021, 2022, 2023 Maurice Lambert - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. - -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. - -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -################### - -"""# This tool runs CLI scripts and displays output in a Web Interface. - -This file can delete user and print the deleted user.""" - -__version__ = "0.0.1" -__author__ = "Maurice Lambert" -__author_email__ = "mauricelambert434@gmail.com" -__maintainer__ = "Maurice Lambert" -__maintainer_email__ = "mauricelambert434@gmail.com" -__description__ = """ -This tool runs CLI scripts and displays output in a Web Interface. - -This file can delete user and print the deleted user.""" -__license__ = "GPL-3.0 License" -__url__ = "https://github.com/mauricelambert/WebScripts" - -copyright = """ -WebScripts Copyright (C) 2021, 2022, 2023 Maurice Lambert -This program comes with ABSOLUTELY NO WARRANTY. -This is free software, and you are welcome to redistribute it -under certain conditions. -""" -license = __license__ -__copyright__ = copyright - -__all__ = [] - -from modules.manage_defaults_databases import delete_user -import sys - - -def main() -> None: - if len(sys.argv) == 2 and sys.argv[1].isdigit(): - user = delete_user(int(sys.argv[1])) - - if user is None: - print(f"User ID: {sys.argv[1]} doesn't exist.") - sys.exit(2) - - print( - f"Deleted user:\n\t - Name: {user.name}\n\t - ID: {user.ID}\n\t -" - f" IPs: {user.IPs}\n\t - Groups: {user.groups}" - ) - else: - print("USAGE: delete_user.py [ID integer required]") - sys.exit(1) - - -if __name__ == "__main__": - main() - sys.exit(0) diff --git a/scripts/account/get_apikey.py b/scripts/account/get_apikey.py deleted file mode 100644 index 11c2f9ce..00000000 --- a/scripts/account/get_apikey.py +++ /dev/null @@ -1,74 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -################### -# This file can print the API key of the current user -# Copyright (C) 2021, 2022, 2023 Maurice Lambert - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. - -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. - -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -################### - -"""# This tool runs CLI scripts and displays output in a Web Interface. - -This file can print the API key of the current user.""" - -__version__ = "0.0.1" -__author__ = "Maurice Lambert" -__author_email__ = "mauricelambert434@gmail.com" -__maintainer__ = "Maurice Lambert" -__maintainer_email__ = "mauricelambert434@gmail.com" -__description__ = """ -This tool runs CLI scripts and displays output in a Web Interface. - -This file can print the API key of the current user.""" -__license__ = "GPL-3.0 License" -__url__ = "https://github.com/mauricelambert/WebScripts" - -copyright = """ -WebScripts Copyright (C) 2021, 2022, 2023 Maurice Lambert -This program comes with ABSOLUTELY NO WARRANTY. -This is free software, and you are welcome to redistribute it -under certain conditions. -""" -license = __license__ -__copyright__ = copyright - -__all__ = [] - -from modules.manage_defaults_databases import get_apikey -from os import environ -import json -import sys - - -def main() -> None: - """Main function to print the API key of the current user.""" - - if len(sys.argv) != 2: - print("USAGE: get_apikey.py [password string required]") - sys.exit(1) - - user_id = str(json.loads(environ["USER"])["id"]) - apikey = get_apikey(user_id, sys.argv[1]) - - if apikey is None: - print("Authentication failed") - sys.exit(2) - - print(apikey) - - -if __name__ == "__main__": - main() - sys.exit(0) diff --git a/scripts/account/modules/manage_defaults_databases.py b/scripts/account/modules/manage_defaults_databases.py deleted file mode 100644 index ccb8ccea..00000000 --- a/scripts/account/modules/manage_defaults_databases.py +++ /dev/null @@ -1,541 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -################### -# This file implement some functions to manage WebScript default databases -# Copyright (C) 2021, 2022, 2023 Maurice Lambert - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. - -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. - -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -################### - -""" -This tool runs CLI scripts and displays output in a Web Interface. - -This file implement some functions to manage WebScript default databases. -""" - -__version__ = "2.0.2" -__author__ = "Maurice Lambert" -__author_email__ = "mauricelambert434@gmail.com" -__maintainer__ = "Maurice Lambert" -__maintainer_email__ = "mauricelambert434@gmail.com" -__description__ = """ -This tool runs CLI scripts and displays output in a Web Interface. - -This file implements functions to manage WebScript default user database -""" -__license__ = "GPL-3.0 License" -__url__ = "https://github.com/mauricelambert/WebScripts" - -copyright = """ -WebScripts Copyright (C) 2021, 2022, 2023 Maurice Lambert -This program comes with ABSOLUTELY NO WARRANTY. -This is free software, and you are welcome to redistribute it -under certain conditions. -""" -license = __license__ -__copyright__ = copyright - -__all__ = [ - "User", - "Group", - "add_user", - "add_group", - "get_users", - "UserError", - "GroupError", - "get_groups", - "get_apikey", - "delete_user", - "delete_group", - "get_dict_groups", - "change_user_password", - "create_default_databases", -] - -from secrets import randbelow, token_bytes -from csv import reader, writer, QUOTE_ALL -from base64 import b64encode, b64decode -from collections.abc import Iterator -from typing import List, Dict, Union -from collections import namedtuple -from hmac import compare_digest -from hashlib import pbkdf2_hmac -from fnmatch import fnmatch -from os.path import join -from html import escape -from os import environ - -User = namedtuple( - "User", - [ - "ID", - "name", - "password", - "salt", - "enumerations", - "IPs", - "groups", - "apikey", - "categories", - "scripts", - ], -) -Group = namedtuple("Group", ["ID", "name"]) -DIRECTORY = environ["WEBSCRIPTS_DATA_PATH"] -USERFILE = "users.csv" -GROUPFILE = "groups.csv" - - -class UserError(Exception): - """ - This class can raise a UserError. - """ - - -class GroupError(Exception): - """ - This class can raise a GroupError. - """ - - -def upgrade_database() -> None: - """ - This function upgrade the database. - - Add default categories ("*") - Add default scripts ("*") - """ - - users = [] - user_add = users.append - first = True - - with open(join(DIRECTORY, USERFILE), newline="") as csvfile: - csvreader = reader(csvfile, quoting=QUOTE_ALL) - - for row in csvreader: - if len(row) == 8: - row_add = row.append - if first: - row_add("categories") - row_add("scripts") - first = False - else: - row_add("*") - row_add("*") - - user_add(row) - - with open(join(DIRECTORY, USERFILE), "w", newline="") as csvfile: - csvwriter = writer(csvfile, quoting=QUOTE_ALL) - writerow = csvwriter.writerow - [writerow(user) for user in users] - - -def get_users() -> Iterator[User]: - """ - This function get users from CSV database. - """ - - yield from map( - User._make, - reader( - open(join(DIRECTORY, USERFILE), "r", newline=""), - quoting=QUOTE_ALL, - ), - ) - - -def get_groups() -> Iterator[Group]: - """ - This function get groups from CSV database. - """ - - yield from map( - Group._make, - reader( - open(join(DIRECTORY, GROUPFILE), "r", newline=""), - quoting=QUOTE_ALL, - ), - ) - - -def get_dict_groups(by_name: bool = False) -> Dict[str, str]: - """ - This function returns the dict of groups (ID: Name or Name: ID). - """ - - if by_name: - return { - n: i - for i, n in reader( - open(join(DIRECTORY, GROUPFILE), "r", newline=""), - quoting=QUOTE_ALL, - ) - } - return { - i: n - for i, n in reader( - open(join(DIRECTORY, GROUPFILE), "r", newline=""), - quoting=QUOTE_ALL, - ) - } - - -def get_apikey(id_: str, password: str) -> str: - """ - This function returns the API key after verification of authentication. - """ - - for user in get_users(): - if id_ == user.ID: - user = auth_username_password(user.name, password) - - if not user or user.ID == "0": - return None - - return escape(user.apikey) - - -def anti_XSS(named_tuple: namedtuple) -> namedtuple: - """ - This function returns a namedtuple without HTML special characters. - """ - - if not named_tuple: - return named_tuple - - new = {} - for attribut, value in named_tuple._asdict().items(): - new[attribut] = escape(value) - return named_tuple.__class__(**new) - - -def change_user_password( - id_: str, new_password: str, old_password: str = None -) -> User: - """ - This function change a user password. - """ - - users = [] - user_ = None - has_old_password = old_password is not None - - for user in get_users(): - if id_ == user.ID: - user_ = has_old_password and auth_username_password( - user.name, old_password - ) - modify_password = ( - has_old_password - and user_ - and user_[0] != "0" - and user_[0] != "1" - ) or not has_old_password - if modify_password: - enumerations = 90000 + randbelow(20000) - salt = token_bytes(32) - password = b64encode( - pbkdf2_hmac( - "sha512", new_password.encode(), salt, enumerations - ) - ).decode() - - user = user._replace( - password=password, - enumerations=str(enumerations), - salt=b64encode(salt).decode(), - apikey=b64encode(token_bytes(125)).decode(), - ) - else: - raise PermissionError("Password is incorrect.") - - users.append(user) - - if modify_password: - rewrite_users(users) - - return anti_XSS(user_) - - -def add_user( - name: str, - password: str, - groups: List[int], - ips: List[str] = ["*"], - categories: List[str] = ["*"], - scripts: List[str] = ["*"], -) -> User: - """ - This function add user in database or raise a UserError. - """ - - name = escape(name) - for i, user in enumerate(get_users()): - if name == user.name: - raise UserError(f"unique constraint failed: name {name!r} is used") - - enumerations = 90000 + randbelow(20000) - salt = token_bytes(32) - hash_ = b64encode( - pbkdf2_hmac("sha512", password.encode(), salt, enumerations) - ).decode() - user = anti_XSS( - User( - str(i), - name, - hash_, - b64encode(salt).decode(), - str(enumerations), - ",".join(ips), - ",".join([str(group) for group in groups]), - b64encode(token_bytes(125)).decode(), - ",".join(categories), - ",".join(scripts), - ) - ) - - for string in user: - if not string.isprintable(): - raise ValueError(f"Strings must be printable: '{string}' is not.") - - with open(join(DIRECTORY, USERFILE), "a", newline="") as csvfile: - csv_writer = writer(csvfile, quoting=QUOTE_ALL) - csv_writer.writerow(user) - - return user - - -def check_ip(user: User) -> Union[User, None]: - """ - This function performs an IP address - filtering for authentication. - """ - - ip_addresses = environ["REMOTE_IP"].split(", ") - - for glob_ip in user.IPs.split(","): - for ip in ip_addresses: - if fnmatch(ip, glob_ip): - return anti_XSS(user) - - -def auth_username_password(name: str, password: str) -> User: - """ - This function verifies the authentication - with user name and password. - """ - - name = escape(name) - for user in get_users(): - if ( - user.password - and user.name == name - and compare_digest( - pbkdf2_hmac( - "sha512", - password.encode(), - b64decode(user.salt.encode()), - int(user.enumerations), - ), - b64decode(user.password), - ) - ): - return check_ip(user) - - -def auth_apikey(apikey: str) -> User: - """ - This function verifies the authentication - with api key. - """ - - for user in get_users(): - if apikey == user.apikey: - return check_ip(user) - - -def auth( - username: str = None, password: str = None, api_key: str = None -) -> User: - """ - This function returns a User from credentials - (username and password) or an api key. - """ - - if api_key is None: - user = auth_username_password(username, password) - else: - user = auth_apikey(api_key) - - if user is None: - return User( - "0", "Not Authenticated", "", "", "", "*", "0", "", "*", "*" - ) - else: - return user - - -def add_group(name: str, id_: int) -> Group: - """ - This function add group in database or raise a GroupError. - """ - - for group in get_groups(): - if name == group.name: - raise GroupError( - f'unique constraint failed: name "{name}" is used' - ) - elif id_ == group.ID: - raise GroupError(f"unique constraint failed: ID {id_} is used") - - group = anti_XSS(Group(id_, name)) - - for string in group: - if not string.isprintable(): - raise ValueError(f"Strings must be printable: '{string}' is not.") - - with open(join(DIRECTORY, GROUPFILE), "a", newline="") as csvfile: - csv_writer = writer(csvfile, quoting=QUOTE_ALL) - csv_writer.writerow(group) - - return group - - -def delete_user(id_: int) -> User: - """ - This function delete a user by id and return the deleted user. - """ - - users = [] - deleted_user = None - - for user in get_users(): - user_id = user.ID - if user_id != "ID" and int(user_id) == id_: - deleted_user = user - users.append( - User( - user_id, - "", - "", - "", - "0", - "", - "", - "", - "", - "", - ) - ) - else: - users.append(user) - - rewrite_users(users) - - return anti_XSS(deleted_user) - - -def rewrite_users(users: List[User]) -> None: - """ - This function rewrite a list of User. - """ - - with open(join(DIRECTORY, USERFILE), "w", newline="") as csvfile: - csv_writer = writer(csvfile, quoting=QUOTE_ALL) - # csv_writer.writerow(User._fields) - writerow = csv_writer.writerow - [writerow(anti_XSS(user)) for user in users] - - -def delete_group(id_: int) -> Group: - """ - This function delete a group by id and return the deleted group. - """ - - groups = [] - deleted_group = None - - for group in get_groups(): - group_id = group.ID - if group_id != "ID" and int(group_id) == id_: - deleted_group = group - else: - groups.append(group) - - with open(join(DIRECTORY, GROUPFILE), "w", newline="") as csvfile: - csv_writer = writer(csvfile, quoting=QUOTE_ALL) - # csv_writer.writerow(Group._fields) - writerow = csv_writer.writerow - [writerow(anti_XSS(group)) for group in groups] - - return anti_XSS(deleted_group) - - -def create_default_databases() -> None: - """ - This function create defaults users and groups. - """ - - default_users = [ - User("0", "Not Authenticated", "", "", "", "*", "0", "", "*", "*"), - User("1", "Unknow", "", "", "", "*", "0,1", "", "*", "*"), - User( - "2", - "Admin", - "pZo8c8+cKLTHFaUBxGwcYaFDgNRw9HHph4brixOo" - "6OMusFKbfkBEObZiNwda/f9W3+IpiMY8kqiFmQcbkUCbGw==", - "c2FsdA==", - "1000", - "192.168.*,172.16.*,10.*,127.0.*", - "50,1000", - "Admin" * 32, - "*", - "*", - ), - ] - default_groups = [ - Group("0", "Not Authenticated"), - Group("1", "Unknow"), - Group("50", "User"), - Group("500", "Developers"), - Group("750", "Maintainers"), - Group("1000", "Administrators"), - ] - - with open(join(DIRECTORY, USERFILE), "w", newline="") as csvfile: - csv_writer = writer(csvfile, quoting=QUOTE_ALL) - writerow = csv_writer.writerow - - writerow(User._fields) - [writerow(user) for user in default_users] - - with open(join(DIRECTORY, GROUPFILE), "w", newline="") as csvfile: - csv_writer = writer(csvfile, quoting=QUOTE_ALL) - writerow = csv_writer.writerow - - writerow(Group._fields) - [writerow(group) for group in default_groups] - - -if __name__ == "__main__": - create_default_databases() - -try: - for user in get_users(): - break -except TypeError: - upgrade_database() diff --git a/scripts/account/my_user_informations.py b/scripts/account/my_user_informations.py deleted file mode 100644 index e4794554..00000000 --- a/scripts/account/my_user_informations.py +++ /dev/null @@ -1,84 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -################### -# This file adds a new user. -# Copyright (C) 2021, 2022, 2023 Maurice Lambert - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. - -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. - -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -################### - -""" -This tool runs CLI scripts and displays output in a Web Interface. - -This file adds a new user. -""" - -__version__ = "1.0.1" -__author__ = "Maurice Lambert" -__author_email__ = "mauricelambert434@gmail.com" -__maintainer__ = "Maurice Lambert" -__maintainer_email__ = "mauricelambert434@gmail.com" -__description__ = """ -This tool runs CLI scripts and displays output in a Web Interface. - -This file adds a new user. -""" -__license__ = "GPL-3.0 License" -__url__ = "https://github.com/mauricelambert/WebScripts" - -copyright = """ -WebScripts Copyright (C) 2021, 2022, 2023 Maurice Lambert -This program comes with ABSOLUTELY NO WARRANTY. -This is free software, and you are welcome to redistribute it -under certain conditions. -""" -license = __license__ -__copyright__ = copyright - -__all__ = ["main"] - -from modules.manage_defaults_databases import get_dict_groups -from os import environ -from json import loads -from sys import exit - - -def main() -> int: - """ - This function prints user configurations. - """ - - groups = get_dict_groups() - user = loads(environ["USER"]) - - print( - f""" -Username: {user['name']!r} -ID: {user['id']!r} -Groups (defined permissions): \ -{ - ', '.join([f'{groups.get(str(group), "UNKNOWN")!r} ({group})' - for group in user['groups']]) -} -Categories (defined access): {', '.join(user['categories'])} -Scripts (defined access): {', '.join(user['scripts'])} - """ - ) - - return 0 - - -if __name__ == "__main__": - exit(main()) diff --git a/scripts/account/view_groups.py b/scripts/account/view_groups.py deleted file mode 100644 index 323ede43..00000000 --- a/scripts/account/view_groups.py +++ /dev/null @@ -1,109 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -################### -# This file prints groups in a HTML table -# Copyright (C) 2021, 2022, 2023 Maurice Lambert - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. - -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. - -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -################### - -""" -This tool runs CLI scripts and displays output in a Web Interface. - -This file prints groups in a HTML table. -""" - -__version__ = "0.1.0" -__author__ = "Maurice Lambert" -__author_email__ = "mauricelambert434@gmail.com" -__maintainer__ = "Maurice Lambert" -__maintainer_email__ = "mauricelambert434@gmail.com" -__description__ = """ -This tool runs CLI scripts and displays output in a Web Interface. - -This file prints groups in a HTML table.""" -__license__ = "GPL-3.0 License" -__url__ = "https://github.com/mauricelambert/WebScripts" - -copyright = """ -WebScripts Copyright (C) 2021, 2022, 2023 Maurice Lambert -This program comes with ABSOLUTELY NO WARRANTY. -This is free software, and you are welcome to redistribute it -under certain conditions. -""" -license = __license__ -__copyright__ = copyright - -__all__ = [] - -from modules.manage_defaults_databases import get_groups -from argparse import ArgumentParser, Namespace -from sys import stdout, exit, stderr -from csv import writer - - -def parse_args() -> Namespace: - """ - This function parse command line arguments. - """ - - parser = ArgumentParser() - parser.add_argument( - "--ids", - "-i", - help="List of group IDs to display them only.", - nargs="+", - default=[], - ) - parser.add_argument( - "--names", - "-n", - help="List of group names to display them only.", - nargs="+", - default=[], - ) - return parser.parse_args() - - -def main() -> int: - """ - Main function to print users using default manager for group database. - """ - - arguments = parse_args() - - for i, value in enumerate(arguments.ids): - if not value.isdigit(): - print( - f'ERROR: ids must be integer. "{value}"' " is not digits.", - file=stderr, - ) - return 3 - - csv_writer = writer(stdout) - - for group in get_groups(): - if ( - (len(arguments.ids) == 0 and len(arguments.names) == 0) - or (arguments.ids and group.ID in arguments.ids) - or (arguments.names and group.name in arguments.names) - ): - csv_writer.writerow([group.ID, group.name]) - - return 0 - - -if __name__ == "__main__": - exit(main()) diff --git a/scripts/account/view_users.py b/scripts/account/view_users.py deleted file mode 100644 index 5d35470d..00000000 --- a/scripts/account/view_users.py +++ /dev/null @@ -1,116 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -################### -# This file can print users in HTML table -# Copyright (C) 2021, 2022, 2023 Maurice Lambert - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. - -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. - -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -################### - -""" -This tool runs CLI scripts and displays output in a Web Interface. - -This file can print users in HTML table. -""" - -__version__ = "0.1.0" -__author__ = "Maurice Lambert" -__author_email__ = "mauricelambert434@gmail.com" -__maintainer__ = "Maurice Lambert" -__maintainer_email__ = "mauricelambert434@gmail.com" -__description__ = """ -This tool runs CLI scripts and displays output in a Web Interface. - -This file can print users in HTML table.""" -__license__ = "GPL-3.0 License" -__url__ = "https://github.com/mauricelambert/WebScripts" - -copyright = """ -WebScripts Copyright (C) 2021, 2022, 2023 Maurice Lambert -This program comes with ABSOLUTELY NO WARRANTY. -This is free software, and you are welcome to redistribute it -under certain conditions. -""" -license = __license__ -__copyright__ = copyright - -__all__ = [] - -from modules.manage_defaults_databases import get_users -from argparse import ArgumentParser, Namespace -from sys import exit, stdout -from csv import writer - - -def parse_args() -> Namespace: - """ - This function parse command line arguments. - """ - - parser = ArgumentParser() - parser.add_argument( - "--ids", - "-i", - help="List of user IDs to display them only.", - nargs="+", - default=[], - ) - parser.add_argument( - "--names", - "-n", - help="List of user names to display them only.", - nargs="+", - default=[], - ) - return parser.parse_args() - - -def main() -> None: - """ - Main function to print users using default manager for user database. - """ - - arguments = parse_args() - - for i, value in enumerate(arguments.ids): - if not value.isdigit(): - print(f'ERROR: ids must be integer. "{value}" ' "is not digits.") - return 3 - - csv_writer = writer(stdout) - - for user in get_users(): - if ( - (len(arguments.ids) == 0 and len(arguments.names) == 0) - or (arguments.ids and user.ID in arguments.ids) - or (arguments.names and user.name in arguments.names) - ): - csv_writer.writerow( - [ - user.ID, - user.name, - user.IPs, - user.groups, - user.apikey, - user.categories, - user.scripts, - ] - ) - - return 0 - - -if __name__ == "__main__": - exit(main()) diff --git a/scripts/doc/py_doc.py b/scripts/doc/py_doc.py deleted file mode 100644 index 8d14d1b2..00000000 --- a/scripts/doc/py_doc.py +++ /dev/null @@ -1,99 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -################### -# This file write HTML documentation using pydoc -# Copyright (C) 2021, 2022, 2023 Maurice Lambert - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. - -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. - -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -################### - -"""# This tool runs CLI scripts and displays output in a Web Interface. - -This file write HTML documentation using pydoc.""" - -__version__ = "0.0.2" -__author__ = "Maurice Lambert" -__author_email__ = "mauricelambert434@gmail.com" -__maintainer__ = "Maurice Lambert" -__maintainer_email__ = "mauricelambert434@gmail.com" -__description__ = """ -This tool runs CLI scripts and displays output in a Web Interface. - -This file write HTML documentation using pydoc.""" -__license__ = "GPL-3.0 License" -__url__ = "https://github.com/mauricelambert/WebScripts" - -copyright = """ -WebScripts Copyright (C) 2021, 2022, 2023 Maurice Lambert -This program comes with ABSOLUTELY NO WARRANTY. -This is free software, and you are welcome to redistribute it -under certain conditions. -""" -license = __license__ -__copyright__ = copyright - -__all__ = [] - -from pydoc import resolve, html, describe, ErrorDuringImport -from os import path -import sys - - -def main() -> None: - """This file write HTML documentation using pydoc.""" - - if len(sys.argv) != 2: - print("USAGE: py_doc.py [filename string required]") - sys.exit(1) - - COLORS = { - "#ffc8d8": "#EEC477", # pink - # "#ee77aa": "#A57927", - "#ee77aa": "#C89B48", # pink - "#aa55cc": "#75B4AD", # purple - "#7799ee": "#9286C2", # blue - "#55aa55": "#18675F", # green - "#eeaa77": "#A57927", # orange - } - - filename = sys.argv[1] - dirname, name = path.split(path.abspath(filename)) - - sys.path.append(dirname) - name_only, extension = path.splitext(name) - - try: - object, name = resolve(name_only) - page = html.page(describe(object), html.document(object, name)) - - for old_color, new_color in COLORS.items(): - page = page.replace( - f'bgcolor="{old_color}"', f'bgcolor="{new_color}"' - ) - - filename = path.join(path.dirname(__file__), "..", "..", "doc", name) - - with open(f"{filename}.html", "w", encoding="utf-8") as file: - file.write(page) - - except (ImportError, ErrorDuringImport) as value: - print(value) - - del sys.path[-1] - - -if __name__ == "__main__": - main() - sys.exit(0) diff --git a/scripts/logs/log_analysis.py b/scripts/logs/log_analysis.py deleted file mode 100644 index 172ba53b..00000000 --- a/scripts/logs/log_analysis.py +++ /dev/null @@ -1,145 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -################### -# This file displays an HTML table for log and activity analysis -# Copyright (C) 2021, 2022, 2023 Maurice Lambert - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. - -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. - -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -################### - -""" -This tool runs CLI scripts and displays output in a Web Interface. - -This file displays an HTML table for log and activity analysis. -""" - -__version__ = "1.1.0" -__author__ = "Maurice Lambert" -__author_email__ = "mauricelambert434@gmail.com" -__maintainer__ = "Maurice Lambert" -__maintainer_email__ = "mauricelambert434@gmail.com" -__description__ = """ -This tool runs CLI scripts and displays output in a Web Interface. - -This file displays an HTML table for log and activity analysis. -""" -__license__ = "GPL-3.0 License" -__url__ = "https://github.com/mauricelambert/WebScripts" - -copyright = """ -WebScripts Copyright (C) 2021, 2022, 2023 Maurice Lambert -This program comes with ABSOLUTELY NO WARRANTY. -This is free software, and you are welcome to redistribute it -under certain conditions. -""" -license = __license__ -__copyright__ = copyright - -__all__ = ["write_line", "write_csv", "main"] - -from collections import Counter -from typing import Dict, List -from sys import exit, stdout -from os import environ -from csv import writer - - -def write_line( - csv_writer: writer, - date: str, - dates: List[str], - columns: List[str], - table: Dict[str, Dict[str, int]], -) -> None: - """ - This function creates an HTML table row. - """ - - if date not in dates: - dates.add(date) - csv_writer.writerow( - [ - ( - date[1:] - if column == "date" - else str(table[column].get(date, 0)) - ) - for column in columns - ] - ) - - -def write_csv(table: Dict[str, Dict[str, int]]) -> None: - """ - This function builds the HTML table. - """ - - columns = ["date", *table.keys()] - csv_writer = writer(stdout) - csv_writer.writerow(columns) - - dates = set() - - for dates_ in table.values(): - for date in dates_.keys(): - write_line(csv_writer, date, dates, columns, table) - - -def main() -> int: - """ - This function read the logfile and parse lines. - """ - - table = {} - - for level in environ["WEBSCRIPTS_LOGS_FILES"].split("|"): - level, filename = level.split("?", 1) - - if level.casefold() == "all": - break - - with open(filename) as logfile: - readline = logfile.readline - line = readline() - - while line != "": - line = line.split(maxsplit=4) - - if len(line) == 5 and line[2] in ( - "DEBUG", - "INFO", - "ACCESS", - "RESPONSE", - "COMMAND", - "WARNING", - "ERROR", - "CRITICAL", - ): - date, time, level, level_no, log = line - else: - line = readline() - continue - - table_level = table.setdefault(level, Counter()) - table_level[date] += 1 - - line = readline() - - write_csv(table) - return 0 - - -if __name__ == "__main__": - exit(main()) diff --git a/scripts/logs/log_viewer.py b/scripts/logs/log_viewer.py deleted file mode 100644 index 574b5829..00000000 --- a/scripts/logs/log_viewer.py +++ /dev/null @@ -1,108 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -################### -# This file can display the latest logs -# Copyright (C) 2021, 2022, 2023 Maurice Lambert - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. - -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. - -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -################### - -""" -This tool runs CLI scripts and displays output in a Web Interface. - -This file can display the latest logs. -""" - -__version__ = "1.0.0" -__author__ = "Maurice Lambert" -__author_email__ = "mauricelambert434@gmail.com" -__maintainer__ = "Maurice Lambert" -__maintainer_email__ = "mauricelambert434@gmail.com" -__description__ = """ -This tool runs CLI scripts and displays output in a Web Interface. - -This file can display the latest logs. -""" -__license__ = "GPL-3.0 License" -__url__ = "https://github.com/mauricelambert/WebScripts" - -copyright = """ -WebScripts Copyright (C) 2021, 2022, 2023 Maurice Lambert -This program comes with ABSOLUTELY NO WARRANTY. -This is free software, and you are welcome to redistribute it -under certain conditions. -""" -license = __license__ -__copyright__ = copyright - -__all__ = [] - -from sys import exit, stderr, argv -from collections import deque -from os import environ - - -def main() -> int: - """ - Main function to display the latest logs. - """ - - length = len(argv) < 2 or argv[1] - - if not length or not length.isdigit(): - print( - "USAGE: log_viewer.py [length required int] [level1 required " - "string] [levelX optional string]..." - "\n\tPossible values for files:\n\t\t - all\n\t\t - DEBUG\n\t\t" - " - INFO\n\t\t - ACCESS\n\t\t - RESPONSE\n\t\t - COMMAND" - "\n\t\t - WARNING\n\t\t - ERROR\n\t\t - CRITICAL\n\t\t - TRACE", - file=stderr, - ) - print( - "ERROR: argument length is required and must be an " - "integer, and a minimum of one level is required", - file=stderr, - ) - return 1 - - length = int(length) - del argv[1] - del argv[0] - - levels = {} - for level in environ["WEBSCRIPTS_LOGS_FILES"].split("|"): - level, filename = level.split("?", 1) - levels[level.casefold()] = filename - - unknow_argument = [] - - for level in argv: - filename = levels.get(level.casefold()) - - if filename is None: - unknow_argument.append(filename) - continue - - with open(filename) as logfile: - print("".join(deque(logfile, length))) - - if len(unknow_argument) != 0: - print(f"ERROR: unexpected arguments {unknow_argument}", file=stderr) - - return 0 - - -if __name__ == "__main__": - exit(main()) diff --git a/scripts/passwords/get_password_share.py b/scripts/passwords/get_password_share.py deleted file mode 100644 index a43c0c27..00000000 --- a/scripts/passwords/get_password_share.py +++ /dev/null @@ -1,192 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -################### -# This file can decrypt and print a secure password share -# Copyright (C) 2021, 2022, 2023 Maurice Lambert - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. - -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. - -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -################### - -"""# This tool runs CLI scripts and displays output in a Web Interface. - -This file can decrypt and print a secure password share.""" - -__version__ = "0.0.2" -__author__ = "Maurice Lambert" -__author_email__ = "mauricelambert434@gmail.com" -__maintainer__ = "Maurice Lambert" -__maintainer_email__ = "mauricelambert434@gmail.com" -__description__ = """ -This tool runs CLI scripts and displays output in a Web Interface. - -This file can decrypt and print a secure password share.""" -__license__ = "GPL-3.0 License" -__url__ = "https://github.com/mauricelambert/WebScripts" - -copyright = """ -WebScripts Copyright (C) 2021, 2022, 2023 Maurice Lambert -This program comes with ABSOLUTELY NO WARRANTY. -This is free software, and you are welcome to redistribute it -under certain conditions. -""" -license = __license__ -__copyright__ = copyright - -__all__ = ["save", "delete", "decrypt", "get_passwords", "main"] - -from typing import TypeVar, Tuple, List -from hmac import compare_digest -from hashlib import pbkdf2_hmac -from csv import reader, writer -from base64 import b64decode -from os import path, chdir -from time import time -from sys import argv -import sys -import csv - -PasswordOrFalse = TypeVar("PasswordOrFalse", str, bool) -PasswordInfo = TypeVar("PasswordInfo", str, int, bool) -filename = path.join("data", "passwords.csv") - - -def decrypt( - key: bytes, password: bytes, hash_: str, iteration: int -) -> PasswordOrFalse: - """This function checks the integrity of the - password and returns the decrypted password.""" - - password_ = "" # nosec - key_length = len(key) - for i, car in enumerate(password): - password_ += chr(key[i % key_length] ^ car) - - if compare_digest( - pbkdf2_hmac("sha512", password_.encode(), key, int(iteration)).hex(), - hash_, - ): - return password_ - else: - return False - - -def get_passwords( - id_: int, -) -> Tuple[List[List[PasswordInfo]], List[PasswordInfo]]: - """This function returns a list of passwords - and the requested password.""" - - now = time() - password_ = None - - with open(filename, newline="") as file: - passwords = list(reader(file, quoting=csv.QUOTE_ALL)) - - for password in passwords: - if float(password[0]) >= now: - password.append(True) - else: - password.append(False) - - if id_ == int(password[5]): - password_ = password - - return passwords, password_ - - -def main() -> None: - """Main function to obtain secure password sharing: - - Check the validity of the token - - Check the validity of the password - - Print password or error message""" - - chdir(path.join(path.dirname(__file__), "..", "..")) - - if len(argv) != 2: - print("USAGE: python3 get_password_share.py [token string required]") - sys.exit(1) - - token = sys.argv[1] - bad_token = ":" not in token - - if not bad_token: - id_, key = token.split(":") - bad_token = not id_.isdigit() - - if bad_token: - print("TOKEN ERROR: Token is not valid.") - sys.exit(2) - - id_ = int(id_) - key = key.encode() - - passwords, password = get_passwords(id_) - - if not password: - print("TOKEN ERROR: the password for this token does not exist.") - sys.exit(3) - - if password[-1]: - password_ = decrypt( - b64decode(key), b64decode(password[1]), password[3], password[4] - ) - else: - print( - "TIME ERROR: The password for this token is no longer available." - ) - sys.exit(4) - - if not password_: - print("TOKEN ERROR: Bad key.") - sys.exit(5) - - password[2] = int(password[2]) - - if password[2]: - password[2] -= 1 - else: - print( - "VIEWS ERROR: This password has been requested too many " - "times, you can no longer access it." - ) - sys.exit(6) - - passwords = delete(passwords) - save(passwords) - - print(password_) - - -def delete(passwords: List[List[PasswordInfo]]) -> List[List[PasswordInfo]]: - """This function delete old passwords.""" - - for password in passwords: - if not password[-1]: - passwords.remove(password) - return passwords - - -def save(passwords: List[List[PasswordInfo]]) -> None: - """This function re-saves passwords.""" - - with open(filename, "w", newline="") as file: - csvfile = writer(file, quoting=csv.QUOTE_ALL) - for password in passwords: - csvfile.writerow(password[:-1]) - - -if __name__ == "__main__": - main() - sys.exit(0) diff --git a/scripts/passwords/new_password_share.py b/scripts/passwords/new_password_share.py deleted file mode 100644 index a7abfd3f..00000000 --- a/scripts/passwords/new_password_share.py +++ /dev/null @@ -1,203 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -################### -# This file can share a password securely -# Copyright (C) 2021, 2022, 2023, 2024 Maurice Lambert - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. - -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. - -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -################### - -""" -This tool run scripts and display the result in a Web Interface. - -This file can share a password securely. -""" - -__version__ = "1.0.2" -__author__ = "Maurice Lambert" -__author_email__ = "mauricelambert434@gmail.com" -__maintainer__ = "Maurice Lambert" -__maintainer_email__ = "mauricelambert434@gmail.com" -__description__ = """ -This tool run scripts and display the result in a Web Interface. - -This file can share a password securely. -""" -__license__ = "GPL-3.0 License" -__url__ = "https://github.com/mauricelambert/WebScripts" - -copyright = """ -WebScripts Copyright (C) 2021, 2022, 2023, 2024 Maurice Lambert -This program comes with ABSOLUTELY NO WARRANTY. -This is free software, and you are welcome to redistribute it -under certain conditions. -""" -license = __license__ -__copyright__ = copyright - -__all__ = [ - "encrypt", - "get_passwords", - "save", - "get_id", - "get_printable", - "main", -] - -from secrets import token_bytes, randbelow -from csv import reader, writer, QUOTE_ALL -from os import path, chdir, environ -from sys import argv, exit, stderr -from hashlib import pbkdf2_hmac -from urllib.parse import quote -from typing import Tuple, List -from base64 import b64encode -from time import time - - -# csv format: timestamp,password,view_number,hash,iterations,id -filename = path.join("data", "passwords.csv") -file_id = path.join("data", "id") -SIZE = 60 - - -def encrypt(password: str) -> Tuple[bytes, str, int, bytes]: - """ - This function encrypts and hashes the password. - """ - - key = token_bytes(SIZE) - cipher = [] - iteration = 9999 + randbelow(5001) - encoded_password = [] - - for i, car in enumerate(password): - car = ord(car) - encoded_password.append(car) - cipher.append(key[i % SIZE] ^ car) - - hash_ = pbkdf2_hmac( - "sha512", bytes(encoded_password), key, iteration - ).hex() - return bytes(cipher), hash_, iteration, key - - -def get_passwords() -> List[List[str]]: - """ - This function returns a list of encrypted passwords. - """ - - with open(filename, newline="") as file: - passwords = list(reader(file, quoting=QUOTE_ALL)) - return passwords - - -def save(passwords: List[List[str]], id_: int) -> None: - """ - This function save passwords and ID. - """ - - with open(filename, "w", newline="") as file: - csvfile = writer(file, quoting=QUOTE_ALL) - for password in passwords: - csvfile.writerow(password) - - with open(file_id, "w", newline="") as file: - file.write(str(id_)) - - -def get_id() -> int: - """ - This function return the password ID. - """ - - if not path.isfile(file_id): - with open(file_id, "w") as file: - file.write("0") - - with open(file_id) as file: - id_ = file.read() - return int(id_) + 1 - - -def get_printable(password: bytes, key: bytes) -> Tuple[str, str]: - """ - This function return a printable password and key (using base64). - """ - - password = b64encode(password).decode() - key = b64encode(key).decode() - return password, key - - -def get_url(token: str) -> str: - """ - This returns the relative URL to get - the password share. - """ - - return "./get_password_share.py?token=" + quote(token) - - -def main() -> int: - """ - Main function to add secure password sharing. - """ - - chdir(path.join(path.dirname(__file__), "..", "..")) - - if ( - len(argv) != 4 - or not argv[3].isdigit() - or not argv[2].replace(".", "", 1).isdigit() - ): - print( - "USAGE: python3 new_password_share.py [password string required] " - "[time_in_hours float required] [maximum_number_of_views " - "integer required]", - file=stderr, - ) - return 1 - - password, hours, views = argv[1], float(argv[2]), int(argv[3]) - - passwords = get_passwords() - timestamp = time() + (hours * 3600) - id_ = get_id() - - password, hash_, iteration, key = encrypt(password) - password, key = get_printable(password, key) - - password = [timestamp, password, views, hash_, iteration, id_] - - for string in passwords: - if isinstance(string, str) and not string.isprintable(): - print( - f"Strings must be printable: {string!r} is not.", file=stderr - ) - return 2 - - passwords.append(password) - save(passwords, id_) - - print( - f'Click on this link or ' - "copy it to access to the password." - ) - return 0 - - -if __name__ == "__main__": - exit(main()) diff --git a/scripts/passwords/password_generator.py b/scripts/passwords/password_generator.py deleted file mode 100644 index f647c2c7..00000000 --- a/scripts/passwords/password_generator.py +++ /dev/null @@ -1,59 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -################### -# This file prints a random ASCII password -# Copyright (C) 2021, 2022, 2023 Maurice Lambert - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. - -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. - -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -################### - -"""# This tool runs CLI scripts and displays output in a Web Interface. - -This file prints a random ASCII password.""" - -__version__ = "0.0.2" -__author__ = "Maurice Lambert" -__author_email__ = "mauricelambert434@gmail.com" -__maintainer__ = "Maurice Lambert" -__maintainer_email__ = "mauricelambert434@gmail.com" -__description__ = """ -This tool runs CLI scripts and displays output in a Web Interface. - -This file prints a random ASCII password.""" -__license__ = "GPL-3.0 License" -__url__ = "https://github.com/mauricelambert/WebScripts" - -copyright = """ -WebScripts Copyright (C) 2021, 2022, 2023 Maurice Lambert -This program comes with ABSOLUTELY NO WARRANTY. -This is free software, and you are welcome to redistribute it -under certain conditions. -""" -license = __license__ -__copyright__ = copyright - -__all__ = [] - -from secrets import randbelow, choice -import sys - -characters = ( - "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOP" - "QRSTUVWXYZ!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~" -) - -print("".join([choice(characters) for i in range(20 + randbelow(15))])) - -sys.exit(0) diff --git a/scripts/py/ASCII_arts.py b/scripts/py/ASCII_arts.py deleted file mode 100644 index 09c28414..00000000 --- a/scripts/py/ASCII_arts.py +++ /dev/null @@ -1,25 +0,0 @@ -print( - """\x1bcuser@host:/home/user/$ python3 script.py -\x1b[38;2;255;194;106m\x1b[48;2;51;51;51m - █████ ███ █████ █████ █████████ ███ █████ -░░███ ░███ ░░███ ░░███ ███░░░░░███ ░░░ ░░███ - ░███ ░███ ░███ ██████ ░███████ ░███ ░░░ ██████ ████████ ████ ████████ ███████ █████ - ░███ ░███ ░███ ███░░███ ░███░░███░░█████████ ███░░███░░███░░███░░███ ░░███░░███░░░███░ ███░░ - ░░███ █████ ███ ░███████ ░███ ░███ ░░░░░░░░███░███ ░░░ ░███ ░░░ ░███ ░███ ░███ ░███ ░░█████ - ░░░█████░█████░ ░███░░░ ░███ ░███ ███ ░███░███ ███ ░███ ░███ ░███ ░███ ░███ ███ ░░░░███ - ░░███ ░░███ ░░██████ ████████ ░░█████████ ░░██████ █████ █████ ░███████ ░░█████ ██████ - ░░░ ░░░ ░░░░░░ ░░░░░░░░ ░░░░░░░░░ ░░░░░░ ░░░░░ ░░░░░ ░███░░░ ░░░░░ ░░░░░░ - ░███ - █████ - ░░░░░ -By Maurice LAMBERT. \x1b[0m -\x1b[32m -[+] 20% |████ | Job1 running... -[+] 30% |██████ | Job2 running... -[+] 60% |████████████ | Job3 running... -[+] 10% |██ | Job4 running...\x1b[31m -[X] 95% |XXXXXXXXXXXXXXXXXXX | Job5 error...\x1b[0m -""" -) - -# \x1b[48;2;255;194;106m\x1b[38;2;34;34;34m diff --git a/scripts/py/hello.py b/scripts/py/hello.py deleted file mode 100644 index 26235657..00000000 --- a/scripts/py/hello.py +++ /dev/null @@ -1,36 +0,0 @@ -def hello( - environ, user, configuration, filename, arguments, inputs, csrf_token=None -): - return "200 OK", {"Content-Type": "text/plain"}, f"Hello {user.name} !" - - -def page_500(error): - return ( - error, - {"Content-Type": "text/html; charset=utf-8"}, - [b"

ERROR 500



\n\n", error.encode()], - ) - - -def page_401(error): - return ( - error, - {"Content-Type": "text/html; charset=utf-8"}, - [b"

ERROR 401



\n\n", error.encode()], - ) - - -def page_403(error): - return ( - error, - {"Content-Type": "text/html; charset=utf-8"}, - [b"

ERROR 403



\n\n", error.encode()], - ) - - -def page_404(error): - return ( - error, - {"Content-Type": "text/html; charset=utf-8"}, - [b"

ERROR 404



\n\n", error.encode()], - ) diff --git a/scripts/py/show_license.py b/scripts/py/show_license.py deleted file mode 100644 index 5515c22c..00000000 --- a/scripts/py/show_license.py +++ /dev/null @@ -1,113 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -################### -# This file may display the license and copyright of WebScripts -# Copyright (C) 2021, 2022, 2023 Maurice Lambert - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. - -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. - -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -################### - -""" -This tool runs CLI scripts and displays output in a Web Interface. - -This file may display the license and copyright of WebScripts. -""" - -__version__ = "0.0.3" -__author__ = "Maurice Lambert" -__author_email__ = "mauricelambert434@gmail.com" -__maintainer__ = "Maurice Lambert" -__maintainer_email__ = "mauricelambert434@gmail.com" -__description__ = """ -This tool runs CLI scripts and displays output in a Web Interface. - -This file may display the license and copyright of WebScripts. -""" -__license__ = "GPL-3.0 License" -__url__ = "https://github.com/mauricelambert/WebScripts" - -copyright = """ -WebScripts Copyright (C) 2021, 2022, 2023 Maurice Lambert -This program comes with ABSOLUTELY NO WARRANTY. -This is free software, and you are welcome to redistribute it -under certain conditions. -""" -license = __license__ -__copyright__ = copyright - -__all__ = [] - -from sys import exit, argv, stderr -from os.path import dirname, join -from os import chdir - - -def main() -> int: - """ - Main function to display license and copyright of WebScripts. - """ - - chdir(join(dirname(__file__), "..", "..")) - - if len(argv) < 2: - print( - "USAGE: show_license.py [part required string]", - file=stderr, - ) - return 1 - - remove = argv.remove - - if "copyright" in argv: - remove("copyright") - print(copyright) - - if "license" in argv: - remove("license") - with open("LICENSE.txt") as license: - print(license.read()) - - if "codeheader" in argv: - remove("codeheader") - print( - """ Copyright (C) 2021, 2022, 2023 Maurice Lambert - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - """ - ) - - if len(argv) > 1: - print( - f"ERROR: unexpected arguments {argv[1:]}", - file=stderr, - ) - return 2 - - return 0 - - -if __name__ == "__main__": - exit(main()) diff --git a/scripts/py/test_config.py b/scripts/py/test_config.py deleted file mode 100644 index 41c2919a..00000000 --- a/scripts/py/test_config.py +++ /dev/null @@ -1,33 +0,0 @@ -from sys import argv, stdin, stderr, exit -from functools import partial -from time import sleep -from os import environ - -# print("Only error...", file=stderr) -# exit(1) - -print = partial(print, flush=True) - -print("Arguments:", " ".join(argv)) -inputs = stdin.read() -print("Inputs:") -print(inputs) -print("Inputs end.") - -print(f"Log path: {environ['WEBSCRIPTS_LOGS_PATH']}") - -sleep(5) -print("5 seconds...") -sleep(5) -print("10 seconds...") -sleep(5) -print("15 seconds...") -stderr.write("My custom error (15 seconds)...") -stderr.flush() - -if "-t" in argv or "--timeout" in argv: - print("TimeoutError is comming... (please wait)...") - sleep(20) - print("After timeout error...") - -print("end.") diff --git a/scripts/request/delete_request.py b/scripts/request/delete_request.py deleted file mode 100644 index 216bfd3a..00000000 --- a/scripts/request/delete_request.py +++ /dev/null @@ -1,81 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -################### -# This file deletes a user request -# Copyright (C) 2021, 2022, 2023 Maurice Lambert - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. - -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. - -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -################### - -"""# This tool runs CLI scripts and displays output in a Web Interface. - -This file deletes a user request.""" - -__version__ = "0.0.1" -__author__ = "Maurice Lambert" -__author_email__ = "mauricelambert434@gmail.com" -__maintainer__ = "Maurice Lambert" -__maintainer_email__ = "mauricelambert434@gmail.com" -__description__ = """ -This tool runs CLI scripts and displays output in a Web Interface. - -This file deletes a user request""" -__license__ = "GPL-3.0 License" -__url__ = "https://github.com/mauricelambert/WebScripts" - -copyright = """ -WebScripts Copyright (C) 2021, 2022, 2023 Maurice Lambert -This program comes with ABSOLUTELY NO WARRANTY. -This is free software, and you are welcome to redistribute it -under certain conditions. -""" -license = __license__ -__copyright__ = copyright - -__all__ = [] - -from modules.requests_management import delete_request -from time import localtime, strftime -import sys - - -def main() -> None: - """Print and delete the user request.""" - - if len(sys.argv) != 2: - print("USAGES: python3 delete_request.py ID") - sys.exit(0) - - try: - request = delete_request(sys.argv[1]) - except Exception as e: - print(f"{e.__class__.__name__}: {e}") - sys.exit(127) - - print( - f"You delete this user request:\n\t" - f'The user "{request.UserName}" get an error code {request.ErrorCode}' - f' on "{request.Page}".\n\tThe user agent ' - f'used for the request creation is "{request.UserAgent}".\n' - f"\n\tName: {request.Name}\n\tTime: " - f"{strftime('%Y-%m-%d %H:%M:%S', localtime(float(request.Time)))}" - f"\n\tSubject: {request.Subject}" - f"\n\tReason: {request.Reason}" - ) - - -if __name__ == "__main__": - main() - sys.exit(0) diff --git a/scripts/request/get_request.py b/scripts/request/get_request.py deleted file mode 100644 index d5d85e0a..00000000 --- a/scripts/request/get_request.py +++ /dev/null @@ -1,80 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -################### -# This file prints a user request -# Copyright (C) 2021, 2022, 2023 Maurice Lambert - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. - -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. - -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -################### - -"""# This tool runs CLI scripts and displays output in a Web Interface. - -This file prints a user request.""" - -__version__ = "0.0.1" -__author__ = "Maurice Lambert" -__author_email__ = "mauricelambert434@gmail.com" -__maintainer__ = "Maurice Lambert" -__maintainer_email__ = "mauricelambert434@gmail.com" -__description__ = """ -This tool runs CLI scripts and displays output in a Web Interface. - -This file prints a user request""" -__license__ = "GPL-3.0 License" -__url__ = "https://github.com/mauricelambert/WebScripts" - -copyright = """ -WebScripts Copyright (C) 2021, 2022, 2023 Maurice Lambert -This program comes with ABSOLUTELY NO WARRANTY. -This is free software, and you are welcome to redistribute it -under certain conditions. -""" -license = __license__ -__copyright__ = copyright - -__all__ = [] - -from modules.requests_management import get_request -from time import localtime, strftime -import sys - - -def main() -> None: - """Print the user request.""" - - if len(sys.argv) != 2: - print("USAGES: python3 get_request.py ID") - sys.exit(0) - - try: - request = get_request(sys.argv[1]) - except Exception as e: - print(f"{e.__class__.__name__}: {e}") - sys.exit(127) - - print( - f'The user "{request.UserName}" get an error code {request.ErrorCode}' - f' on "{request.Page}".\nThe user agent used ' - f'for the request creation is "{request.UserAgent}".\n' - f"\nName: {request.Name}\nTime: " - f"{strftime('%Y-%m-%d %H:%M:%S', localtime(float(request.Time)))}" - f"\nSubject: {request.Subject}" - f"\nReason: {request.Reason}" - ) - - -if __name__ == "__main__": - main() - sys.exit(0) diff --git a/scripts/request/get_requests.py b/scripts/request/get_requests.py deleted file mode 100644 index 92d97354..00000000 --- a/scripts/request/get_requests.py +++ /dev/null @@ -1,101 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -################### -# This file prints a HTML table of user requests -# Copyright (C) 2021, 2022, 2023 Maurice Lambert - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. - -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. - -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -################### - -""" -This tool runs CLI scripts and displays output in a Web Interface. - -This file prints an HTML table of user requests. -""" - -__version__ = "0.2.0" -__author__ = "Maurice Lambert" -__author_email__ = "mauricelambert434@gmail.com" -__maintainer__ = "Maurice Lambert" -__maintainer_email__ = "mauricelambert434@gmail.com" -__description__ = """ -This tool runs CLI scripts and displays output in a Web Interface. - -This file prints a HTML table of user requests""" -__license__ = "GPL-3.0 License" -__url__ = "https://github.com/mauricelambert/WebScripts" - -copyright = """ -WebScripts Copyright (C) 2021, 2022, 2023 Maurice Lambert -This program comes with ABSOLUTELY NO WARRANTY. -This is free software, and you are welcome to redistribute it -under certain conditions. -""" -license = __license__ -__copyright__ = copyright - -__all__ = [] - -from modules.requests_management import get_requests -from time import localtime, strftime -from urllib.parse import quote -from sys import exit, stderr -from html import escape - - -def main() -> int: - """ - Print the HTML table of user requests. - """ - - fields = [ - "ID", - "Time", - "UserName", - "Subject", - "ErrorCode", - "Page", - ] - print( - f"" - "" - ) - - try: - requests = get_requests() - except Exception as e: - print(f"{e.__class__.__name__}: {e}", file=stderr) - return 127 - - not_first = False - for request in requests: - if not_first: - print( - f'" - f"" - f"" - f"" - ) - else: - not_first = True - - print("
{''.join(fields)}
' - f"{escape(request.ID)}" - + strftime("%Y-%m-%d %H:%M:%S", localtime(float(request.Time))) - + f"{escape(request.UserName)}{escape(request.Subject)}{escape(request.ErrorCode)}{escape(request.Page)}
") - - -if __name__ == "__main__": - exit(main()) diff --git a/scripts/request/modules/requests_management.py b/scripts/request/modules/requests_management.py deleted file mode 100644 index 3e040f5e..00000000 --- a/scripts/request/modules/requests_management.py +++ /dev/null @@ -1,141 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -################### -# This file implement some functions to manage requests/reports on -# WebScripts -# Copyright (C) 2021, 2022, 2023 Maurice Lambert - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. - -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. - -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -################### - -"""# This tool runs CLI scripts and displays output in a Web Interface. - -This file implement some functions to manage requests/reports on WebScripts. -""" - -__version__ = "0.1.0" -__author__ = "Maurice Lambert" -__author_email__ = "mauricelambert434@gmail.com" -__maintainer__ = "Maurice Lambert" -__maintainer_email__ = "mauricelambert434@gmail.com" -__description__ = """ -This tool runs CLI scripts and displays output in a Web Interface. - -This file implement some functions to manage requests/reports on WebScripts -""" -__license__ = "GPL-3.0 License" -__url__ = "https://github.com/mauricelambert/WebScripts" - -copyright = """ -WebScripts Copyright (C) 2021, 2022, 2023 Maurice Lambert -This program comes with ABSOLUTELY NO WARRANTY. -This is free software, and you are welcome to redistribute it -under certain conditions. -""" -license = __license__ -__copyright__ = copyright - -__all__ = [ - "Request", - "get_requests", - "get_request", - "delete_request", -] - -from collections.abc import Iterator -from collections import namedtuple -from os import path, replace -from html import escape -import csv - -Request = namedtuple( - "Request", - [ - "ID", - "Time", - "UserName", - "ErrorCode", - "Page", - "UserAgent", - "Subject", - "Reason", - "Name", - ], -) -FILE = "requests.csv" -DIRECTORY = path.join( - path.dirname(__file__), - "..", - "..", - "..", - "data", -) - - -def anti_XSS(named_tuple: namedtuple) -> namedtuple: - """ - This function returns a namedtuple without HTML special characters. - """ - - new = {} - for attribut, value in named_tuple._asdict().items(): - new[attribut] = escape(value) - return named_tuple.__class__(**new) - - -def get_requests() -> Iterator[Request]: - """This function build Request from database.""" - - yield from map( - Request._make, - csv.reader( - open(path.join(DIRECTORY, FILE), "r", newline=""), - quoting=csv.QUOTE_ALL, - ), - ) - - -def get_request(id_: str) -> Request: - """This function return a specific request.""" - - for request in get_requests(): - if request.ID == id_: - return anti_XSS(request) - - raise ValueError("This request ID doesn't exists.") - - -def delete_request(id_: str) -> Request: - """ - This function rewrite the request database without the specified request. - """ - - deleted_request = None - filename = path.join(DIRECTORY, f"{FILE}.new") - - with open(filename, "w", newline="") as file: - csvwriter = csv.writer(file, quoting=csv.QUOTE_ALL) - - for request in get_requests(): - if request.ID == id_: - deleted_request = request - else: - csvwriter.writerow(anti_XSS(request)) - - if deleted_request is None: - raise ValueError("This request ID doesn't exists.") - - replace(filename, path.join(DIRECTORY, FILE)) - return anti_XSS(deleted_request) diff --git a/scripts/rss/add_news.py b/scripts/rss/add_news.py deleted file mode 100644 index 67336ce9..00000000 --- a/scripts/rss/add_news.py +++ /dev/null @@ -1,161 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -################### -# This file adds a news in the RSS feed -# Copyright (C) 2021, 2022, 2023 Maurice Lambert - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. - -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. - -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -################### - -""" -This tool runs CLI scripts and displays output in a Web Interface. - -This file adds a news in the RSS feed -""" - -__version__ = "0.0.2" -__author__ = "Maurice Lambert" -__author_email__ = "mauricelambert434@gmail.com" -__maintainer__ = "Maurice Lambert" -__maintainer_email__ = "mauricelambert434@gmail.com" -__description__ = """ -This tool runs CLI scripts and displays output in a Web Interface. - -This file adds a news in the RSS feed -""" -__license__ = "GPL-3.0 License" -__url__ = "https://github.com/mauricelambert/WebScripts" - -copyright = """ -WebScripts Copyright (C) 2021, 2022, 2023 Maurice Lambert -This program comes with ABSOLUTELY NO WARRANTY. -This is free software, and you are welcome to redistribute it -under certain conditions. -""" -license = __license__ -__copyright__ = copyright - -__all__ = ["parse_args", "main", "get_guid"] - -from argparse import ArgumentParser, Namespace -from csv import writer, reader, QUOTE_ALL -from sys import exit, stdin, stderr -from os.path import join, exists -from base64 import b64decode -from os import environ -from json import loads -from enum import Enum -from time import time - - -class FIELDS(Enum): - guid = 0 - author = 1 - title = 2 - description = 3 - link = 4 - categories = 5 - pubDate = 6 - comments = 7 - - -def parse_args() -> Namespace: - - """ - This function parse command line arguments. - """ - - parser = ArgumentParser( - description="This file adds a news in the RSS feed" - ) - add_argument = parser.add_argument - - add_argument("title", help="The news title") - add_argument("link", help="The news link (with the complete content)") - add_argument( - "categories", - action="extend", - nargs="+", - help="The news categories (for filters)", - ) - - add_argument("-c", "--comments", help="The news comments", default="") - add_argument( - "--is-base64", - "--is-b64", - "-i", - help="Using base64 to upload description", - action="store_true", - default=False, - ) - - return parser.parse_args() - - -def get_guid(csvpath: str) -> int: - - """ - This function returns the new GUID. - """ - - with open(csvpath, "r", newline="") as file: - csvfile = reader(file) - - for line in csvfile: - pass - - return csvfile.line_num - - -def main() -> int: - - """ - The main function to add the news in RSS feed. - """ - - arguments = parse_args() - - csvpath = join(environ["WEBSCRIPTS_DATA_PATH"], "rss.csv") - - if not exists(csvpath): - print("FileNotFoundError: WEBSCRIPTS_DATA_PATH/rss.csv", file=stderr) - return 2 - - if arguments.is_base64: - description = b64decode(stdin.buffer.read()) - else: - description = stdin.read() - - with open(csvpath, "a", newline="") as file: - csvwriter = writer(file, quoting=QUOTE_ALL) - csvwriter.writerow( - ( - get_guid(csvpath), - loads(environ["USER"])["name"], - arguments.title, - description, - arguments.link, - ",".join(arguments.categories), - str(time()), - arguments.comments, - ) - ) - - print("New content added successfully !") - return 0 - - -if __name__ == "__main__": - exit(main()) diff --git a/scripts/to_3.8/to_3.8.py b/scripts/to_3.8/to_3.8.py deleted file mode 100644 index 8c5f1f08..00000000 --- a/scripts/to_3.8/to_3.8.py +++ /dev/null @@ -1,236 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -################### -# This file change the code for python3.8 compatibility -# from the command line and display the result in a web interface. -# Copyright (C) 2021, 2022, 2023 Maurice Lambert - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. - -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. - -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -################### - -""" -This tool runs CLI scripts and displays output in a Web Interface. - -This file change the code for python3.8 compatibility. - -To install WebScripts with python3.8 compatibility -as package run the following commands line: - - python3.8 to_3.8.py - - python3.8 ../../../setup38.py install - -The new package is named WebScripts38. - -Impact: "log_encoding" configuration is not use. -""" - -__version__ = "0.0.4" -__author__ = "Maurice Lambert" -__author_email__ = "mauricelambert434@gmail.com" -__maintainer__ = "Maurice Lambert" -__maintainer_email__ = "mauricelambert434@gmail.com" -__description__ = """ -This tool runs CLI scripts and displays output in a Web Interface. - -This file change the code for python3.8 compatibility. -""" -__license__ = "GPL-3.0 License" -__url__ = "https://github.com/mauricelambert/WebScripts" - -copyright = """ -WebScripts Copyright (C) 2021, 2022, 2023 Maurice Lambert -This program comes with ABSOLUTELY NO WARRANTY. -This is free software, and you are welcome to redistribute it -under certain conditions. -""" -license = __license__ -__copyright__ = copyright - -__all__ = [] - -from shutil import copytree, copyfile -from os import path -import logging -import glob -import sys -import re - - -def copy(directory: str, setup_filename: str) -> None: - - """ - This function copy the WebScripts directory and the setup. - """ - - logging.warning("Copy the WebScripts directory and the setup filename...") - copytree( - path.join(directory, "WebScripts"), - path.join(directory, "WebScripts38"), - ) - copyfile(path.join(directory, "setup.py"), setup_filename) - logging.info("Copy the WebScripts directory and the setup filename...") - - -def change_setup(filename: str) -> None: - - """ - This function change the setup.py file. - """ - - content = open(filename).read() - logging.warning("Change the new setup content...") - - with open(filename, "w") as file: - file.write( - content.replace( - "import WebScripts as package", - "import WebScripts38 as package", - ).replace('python_requires=">=3.9",', 'python_requires=">=3.8",') - ) - - logging.info("New setup is changed.") - - -def change_manifest(filename: str) -> None: - - """ - This function change the manifest.in file. - """ - - content = open(filename).read() - logging.warning("Change the new manifest content...") - - with open(filename, "w") as file: - file.write(content.replace("WebScripts/", "WebScripts38/")) - - logging.info("New manifest is changed.") - - -def change_utils(filename: str): - - """ - This function change the utils.py file. - """ - - logging.warning("Change the new utils.py content...") - content = open(filename).read() - - with open(filename, "w") as file: - file.write( - content.replace( - """from typing import ( - TypeVar, - List, - Dict, - _SpecialGenericAlias, - _GenericAlias, - Any, - Union, -)""", - """from typing import ( - TypeVar, - List, - Dict, - _GenericAlias, - Any, - Union, -)""", - ).replace( - """ or isinstance( - type_, _SpecialGenericAlias - )""", - "", - ) - ) - - logging.info("New utils.py is changed.") - - -def change_WebScripts(filename: str): - - """ - This function change the WebScripts.py file. - """ - - logging.warning("Change the new WebScripts.py content...") - content = open(filename).read() - - with open(filename, "w") as file: - file.write( - content.replace('encoding="utf-8",', "").replace( - '"log_encoding": "encoding",', "" - ) - ) - - logging.info("New WebScripts.py is changed.") - - -def change_subscriptable_iterator(directory: str) -> None: - - """ - This function change subscriptable Iterators. - """ - - regex = re.compile(r"Iterator\[[\w,\s\[\]]+\]") - for filename in glob.glob( - path.join(directory, "WebScripts38", "**", "*.py"), recursive=True - ): - content = open(filename).read() - logging.warning(f"Change the new {filename} content...") - - new_content, number = regex.subn("Iterator", content) - - with open(filename, "w") as file: - file.write(new_content) - - logging.info(f"New {filename} is changed ({number} times).") - - for filename in glob.glob( - path.join(directory, "WebScripts38", "*.py"), recursive=True - ): - content = open(filename).read() - logging.warning(f"Change the new {filename} content...") - - new_content, number = regex.subn("Iterator", content) - - with open(filename, "w") as file: - file.write(new_content) - - logging.info(f"New {filename} is changed ({number} times).") - - -def main(): - - """ - This function execute the file. - """ - - logging.debug("Set the WebScripts directory and the setup filename...") - webscript_dir = path.join(path.dirname(__file__), "..", "..", "..") - new_setup = path.join(webscript_dir, "setup38.py") - - copy(webscript_dir, new_setup) - change_manifest(path.join(webscript_dir, "MANIFEST.in")) - change_setup(new_setup) - change_utils(path.join(webscript_dir, "WebScripts38", "utils.py")) - change_WebScripts( - path.join(webscript_dir, "WebScripts38", "WebScripts.py") - ) - change_subscriptable_iterator(webscript_dir) - - -if __name__ == "__main__": - logging.basicConfig(level=0) - main() - sys.exit(0) diff --git a/scripts/uploads/HTML_all_files.py b/scripts/uploads/HTML_all_files.py deleted file mode 100644 index 6829661c..00000000 --- a/scripts/uploads/HTML_all_files.py +++ /dev/null @@ -1,107 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -################### -# This file prints a HTML table of uploaded files -# Copyright (C) 2021, 2022, 2023, 2024 Maurice Lambert - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. - -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. - -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -################### - -""" -This tool runs CLI scripts and displays output in a Web Interface. - -This file prints a HTML table of uploaded files. -""" - -__version__ = "1.0.1" -__author__ = "Maurice Lambert" -__author_email__ = "mauricelambert434@gmail.com" -__maintainer__ = "Maurice Lambert" -__maintainer_email__ = "mauricelambert434@gmail.com" -__description__ = """ -This tool runs CLI scripts and displays output in a Web Interface. - -This file prints a HTML table of uploaded files -""" -__license__ = "GPL-3.0 License" -__url__ = "https://github.com/mauricelambert/WebScripts" - -copyright = """ -WebScripts Copyright (C) 2021, 2022, 2023, 2024 Maurice Lambert -This program comes with ABSOLUTELY NO WARRANTY. -This is free software, and you are welcome to redistribute it -under certain conditions. -""" -license = __license__ -__copyright__ = copyright - -__all__ = ["main"] - -from modules.uploads_management import get_files -from time import localtime, strftime -from urllib.parse import quote -from sys import exit, stderr -from html import escape - - -def main() -> int: - """ - This function prints the HTML table of uploaded files. - """ - - fields = [ - "name", - "timestamp", - "read_permission", - "write_permission", - "delete_permission", - "user", - ] - print( - f"" - ) - - try: - files = { - file.name: {field: getattr(file, field) for field in fields} - for file in get_files() - } - except Exception as e: - print(f"{e.__class__.__name__}: {e}", file=stderr) - return 127 - - for name, file in files.items(): - print( - # f'" - f"" - f"" - f"" - ) - - print("
{''.join(fields)}" - "
' - # f'{escape(name)}' - '
{escape(name)}' - + strftime( - "%Y-%m-%d %H:%M:%S", localtime(float(file["timestamp"])) - ) - + f"{escape(file['read_permission'])}{escape(file['write_permission'])}{escape(file['delete_permission'])}{escape(file['user'])}
") - - return 0 - - -if __name__ == "__main__": - exit(main()) diff --git a/scripts/uploads/HTML_file_history.py b/scripts/uploads/HTML_file_history.py deleted file mode 100644 index 8803c792..00000000 --- a/scripts/uploads/HTML_file_history.py +++ /dev/null @@ -1,115 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -################### -# This file prints a HTML table of uploaded file versions -# Copyright (C) 2021, 2022, 2023, 2024 Maurice Lambert - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. - -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. - -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -################### - -""" -This tool runs CLI scripts and displays output in a Web Interface. - -This file prints a HTML table of uploaded file versions. -""" - -__version__ = "1.0.0" -__author__ = "Maurice Lambert" -__author_email__ = "mauricelambert434@gmail.com" -__maintainer__ = "Maurice Lambert" -__maintainer_email__ = "mauricelambert434@gmail.com" -__description__ = """ -This tool runs CLI scripts and displays output in a Web Interface. - -This file prints a HTML table of uploaded file versions. -""" -__license__ = "GPL-3.0 License" -__url__ = "https://github.com/mauricelambert/WebScripts" - -copyright = """ -WebScripts Copyright (C) 2021, 2022, 2023, 2024 Maurice Lambert -This program comes with ABSOLUTELY NO WARRANTY. -This is free software, and you are welcome to redistribute it -under certain conditions. -""" -license = __license__ -__copyright__ = copyright - -__all__ = [] - -from modules.uploads_management import get_file -from time import localtime, strftime -from sys import argv, exit, stderr -from urllib.parse import quote -from html import escape - - -def main() -> int: - """ - Print the HTML table of file history. - """ - - if len(argv) != 2: - print("USAGE: get_history.py [FILENAME required string]") - return 1 - - filename = argv[1] - - fields = [ - "ID", - "name", - "read_permission", - "write_permission", - "delete_permission", - "hidden", - "is_deleted", - "is_binary", - "timestamp", - "user", - "version", - ] - print( - f"" - ) - - try: - files, counter = get_file(filename) - except Exception as e: - print(f"{e.__class__.__name__}: {e}", file=stderr) - return 127 - - for file in files: - id_ = file.ID - file = file._replace( - ID='{escape(id_)}', - timestamp=strftime( - "%Y-%m-%d %H:%M:%S", localtime(float(file.timestamp)) - ), - ) - - values = [ - escape(v) if k != "ID" else v for k, v in file._asdict().items() - ] - print(f'") - - print("
{''.join(fields)}" - "
{"".join(values)}' "
") - - return 0 - - -if __name__ == "__main__": - exit(main()) diff --git a/scripts/uploads/HTML_uploads_properties.py b/scripts/uploads/HTML_uploads_properties.py deleted file mode 100644 index 0d668e72..00000000 --- a/scripts/uploads/HTML_uploads_properties.py +++ /dev/null @@ -1,101 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -################### -# This file prints an HTML table of uploaded files sizes. -# Copyright (C) 2021, 2022, 2023 Maurice Lambert - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. - -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. - -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -################### - -""" -This tool runs CLI scripts and displays output in a Web Interface. - -This file prints an HTML table of uploaded files sizes. -""" - -__version__ = "1.0.0" -__author__ = "Maurice Lambert" -__author_email__ = "mauricelambert434@gmail.com" -__maintainer__ = "Maurice Lambert" -__maintainer_email__ = "mauricelambert434@gmail.com" -__description__ = """ -This tool runs CLI scripts and displays output in a Web Interface. - -This file prints an HTML table of uploaded files sizes. -""" -__license__ = "GPL-3.0 License" -__url__ = "https://github.com/mauricelambert/WebScripts" - -copyright = """ -WebScripts Copyright (C) 2021, 2022, 2023 Maurice Lambert -This program comes with ABSOLUTELY NO WARRANTY. -This is free software, and you are welcome to redistribute it -under certain conditions. -""" -license = __license__ -__copyright__ = copyright - -__all__ = ["main"] - -from modules.uploads_management import get_metadata -from datetime import datetime -from sys import exit, stdout -from csv import writer - -fromtimestamp = datetime.fromtimestamp - - -def main() -> int: - """ - This function prints an HTML table of uploaded files sizes. - """ - - csv_write = writer(stdout) - csv_write.writerow( - [ - "Name", - "Full size (ko) (all versions)", - "Size (ko)", - "Number of version", - "Date modification", - "Date creation (OS)", - "Date creation (WebScripts)", - "Date acces", - ] - ) - - for name, metadata in get_metadata().items(): - csv_write.writerow( - [ - name, - str(metadata.full_size / 1000), - str(metadata.last_size / 1000), - metadata.version, - fromtimestamp(metadata.modification).strftime( - "%Y-%d-%d %H:%M:%S" - ), - fromtimestamp(metadata.creation).strftime("%Y-%d-%d %H:%M:%S"), - fromtimestamp(metadata.webscripts_creation).strftime( - "%Y-%d-%d %H:%M:%S" - ), - fromtimestamp(metadata.access).strftime("%Y-%d-%d %H:%M:%S"), - ] - ) - - return 0 - - -if __name__ == "__main__": - exit(main()) diff --git a/scripts/uploads/HTML_uploads_size.py b/scripts/uploads/HTML_uploads_size.py deleted file mode 100644 index 92526b5a..00000000 --- a/scripts/uploads/HTML_uploads_size.py +++ /dev/null @@ -1,83 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -################### -# This file prints an HTML table of uploaded files sizes. -# Copyright (C) 2021, 2022 Maurice Lambert - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. - -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. - -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -################### - -""" -This tool run scripts and display the result in a Web Interface. - -This file prints an HTML table of uploaded files sizes. -""" - -__version__ = "1.0.0" -__author__ = "Maurice Lambert" -__author_email__ = "mauricelambert434@gmail.com" -__maintainer__ = "Maurice Lambert" -__maintainer_email__ = "mauricelambert434@gmail.com" -__description__ = """ -This tool run scripts and display the result in a Web Interface. - -This file prints an HTML table of uploaded files sizes. -""" -__license__ = "GPL-3.0 License" -__url__ = "https://github.com/mauricelambert/WebScripts" - -copyright = """ -WebScripts Copyright (C) 2021, 2022 Maurice Lambert -This program comes with ABSOLUTELY NO WARRANTY. -This is free software, and you are welcome to redistribute it -under certain conditions. -""" -license = __license__ -__copyright__ = copyright - -__all__ = ["main"] - -from modules.uploads_management import get_metadata -from sys import exit - - -def main() -> int: - """ - This function prints an HTML table of uploaded files sizes. - """ - - print( - "" - "" - "" - ) - - for name, metadata in get_metadata().items(): - print( - f"" - f"" - f"" - f"" - ) - - print("
namefull size (all version)" - "sizeNumber of versionmodificationCreation (OS)Creation (WebScripts)Acces
{name}{metadata.full_size}{metadata.last_size}{metadata.version}{metadata.modification}{metadata.creation}{metadata.webscripts_creation}{metadata.access}" - "
") - - return 0 - - -if __name__ == "__main__": - exit(main()) diff --git a/scripts/uploads/HTML_visible_files.py b/scripts/uploads/HTML_visible_files.py deleted file mode 100644 index c3f6bc54..00000000 --- a/scripts/uploads/HTML_visible_files.py +++ /dev/null @@ -1,103 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -################### -# This file prints a HTML table of uploaded files -# Copyright (C) 2021, 2022, 2023, 2024 Maurice Lambert - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. - -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. - -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -################### - -""" -This tool runs CLI scripts and displays output in a Web Interface. - -This file prints a HTML table of uploaded files. -""" - -__version__ = "1.0.1" -__author__ = "Maurice Lambert" -__author_email__ = "mauricelambert434@gmail.com" -__maintainer__ = "Maurice Lambert" -__maintainer_email__ = "mauricelambert434@gmail.com" -__description__ = """ -This tool runs CLI scripts and displays output in a Web Interface. - -This file prints a HTML table of uploaded files -""" -__license__ = "GPL-3.0 License" -__url__ = "https://github.com/mauricelambert/WebScripts" - -copyright = """ -WebScripts Copyright (C) 2021, 2022, 2023, 2024 Maurice Lambert -This program comes with ABSOLUTELY NO WARRANTY. -This is free software, and you are welcome to redistribute it -under certain conditions. -""" -license = __license__ -__copyright__ = copyright - -__all__ = ["main"] - -from modules.uploads_management import get_visible_files -from time import localtime, strftime -from urllib.parse import quote -from sys import exit, stderr -from html import escape - - -def main() -> int: - """ - This function prints the HTML table of uploaded files. - """ - - fields = [ - "name", - "time", - "user", - "read_permission", - "write_permission", - "delete_permission", - ] - print( - f"" - ) - - try: - files = get_visible_files() - except Exception as e: - print(f"{e.__class__.__name__}: {e}", file=stderr) - return 127 - - for file in files.values(): - name = file.name - print( - # f'" - f"" - f"" - f"" - ) - - print("
{''.join(fields)}" - "
' - # f"{escape(file.name)}" - '
{escape(name)}' - + strftime("%Y-%m-%d %H:%M:%S", localtime(float(file.timestamp))) - + f"{escape(file.user)}{escape(file.read_permission)}{escape(file.write_permission)}{escape(file.delete_permission)}
") - - return 0 - - -if __name__ == "__main__": - exit(main()) diff --git a/scripts/uploads/JSON_all_files.py b/scripts/uploads/JSON_all_files.py deleted file mode 100644 index 97d39c81..00000000 --- a/scripts/uploads/JSON_all_files.py +++ /dev/null @@ -1,83 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -################### -# This file prints uploaded files as a JSON object -# Copyright (C) 2021, 2022, 2023 Maurice Lambert - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. - -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. - -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -################### - -""" -This tool runs CLI scripts and displays output in a Web Interface. - -This file prints a JSON object of uploaded files. -""" - -__version__ = "0.1.0" -__author__ = "Maurice Lambert" -__author_email__ = "mauricelambert434@gmail.com" -__maintainer__ = "Maurice Lambert" -__maintainer_email__ = "mauricelambert434@gmail.com" -__description__ = """ -This tool runs CLI scripts and displays output in a Web Interface. - -This file prints a JSON object of uploaded files""" -__license__ = "GPL-3.0 License" -__url__ = "https://github.com/mauricelambert/WebScripts" - -copyright = """ -WebScripts Copyright (C) 2021, 2022, 2023 Maurice Lambert -This program comes with ABSOLUTELY NO WARRANTY. -This is free software, and you are welcome to redistribute it -under certain conditions. -""" -license = __license__ -__copyright__ = copyright - -__all__ = [] - -from modules.uploads_management import get_files -from sys import exit, stdout, stderr -from json import dump - - -def main() -> int: - """ - Print the JSON object of uploaded files. - """ - - fields = [ - "name", - "read_permission", - "write_permission", - "delete_permission", - "user", - ] - - try: - files = { - file.name: {field: getattr(file, field) for field in fields} - for file in get_files() - } - except Exception as e: - print(f"{e.__class__.__name__}: {e}", file=stderr) - return 127 - - dump(files, stdout) - return 0 - - -if __name__ == "__main__": - exit(main()) diff --git a/scripts/uploads/JSON_file_history.py b/scripts/uploads/JSON_file_history.py deleted file mode 100644 index 18f4e4fc..00000000 --- a/scripts/uploads/JSON_file_history.py +++ /dev/null @@ -1,83 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -################### -# This file prints a JSON objects of uploaded file versions -# Copyright (C) 2021, 2022, 2023 Maurice Lambert - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. - -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. - -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -################### - -""" -This tool runs CLI scripts and displays output in a Web Interface. - -This file prints a JSON objects of uploaded file versions. -""" - -__version__ = "1.0.1" -__author__ = "Maurice Lambert" -__author_email__ = "mauricelambert434@gmail.com" -__maintainer__ = "Maurice Lambert" -__maintainer_email__ = "mauricelambert434@gmail.com" -__description__ = """ -This tool runs CLI scripts and displays output in a Web Interface. - -This file prints a JSON object of uploaded file versions""" -__license__ = "GPL-3.0 License" -__url__ = "https://github.com/mauricelambert/WebScripts" - -copyright = """ -WebScripts Copyright (C) 2021, 2022, 2023 Maurice Lambert -This program comes with ABSOLUTELY NO WARRANTY. -This is free software, and you are welcome to redistribute it -under certain conditions. -""" -license = __license__ -__copyright__ = copyright - -__all__ = [] - -from modules.uploads_management import get_file -from sys import exit, argv, stderr, stdout -from json import dump - - -def main() -> int: - """ - Print the JSON objects of file history. - """ - - if len(argv) != 2: - print( - "USAGE: api_get_history.py [FILENAME required string]", file=stderr - ) - return 1 - - filename = argv[1] - - try: - files, counter = get_file(filename) - except Exception as e: - print(f"{e.__class__.__name__}: {e}", file=stderr) - return 127 - - versions = [file._asdict() for file in files] - - dump(versions, stdout) - - return 0 - - -if __name__ == "__main__": - exit(main()) diff --git a/scripts/uploads/JSON_uploads_properties.py b/scripts/uploads/JSON_uploads_properties.py deleted file mode 100644 index cecdd50e..00000000 --- a/scripts/uploads/JSON_uploads_properties.py +++ /dev/null @@ -1,82 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -################### -# This file prints a JSON object of uploaded files sizes. -# Copyright (C) 2021, 2022, 2023 Maurice Lambert - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. - -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. - -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -################### - -""" -This tool runs CLI scripts and displays output in a Web Interface. - -This file prints a JSON object of uploaded files sizes. -""" - -__version__ = "1.0.1" -__author__ = "Maurice Lambert" -__author_email__ = "mauricelambert434@gmail.com" -__maintainer__ = "Maurice Lambert" -__maintainer_email__ = "mauricelambert434@gmail.com" -__description__ = """ -This tool runs CLI scripts and displays output in a Web Interface. - -This file prints a JSON object of uploaded files -""" -__license__ = "GPL-3.0 License" -__url__ = "https://github.com/mauricelambert/WebScripts" - -copyright = """ -WebScripts Copyright (C) 2021, 2022, 2023 Maurice Lambert -This program comes with ABSOLUTELY NO WARRANTY. -This is free software, and you are welcome to redistribute it -under certain conditions. -""" -license = __license__ -__copyright__ = copyright - -__all__ = ["main"] - -from modules.uploads_management import get_metadata -from sys import exit, stdout -from json import dump - - -def main() -> int: - """ - This file prints a JSON object of uploaded files sizes. - """ - - dump( - { - name: { - "full size": metadata.full_size, - "last size": metadata.last_size, - "versions": metadata.version, - "modification": metadata.modification, - "creation (OS)": metadata.creation, - "creation (WebScripts)": metadata.webscripts_creation, - "Access": metadata.access, - } - for name, metadata in get_metadata().items() - }, - stdout, - ) - - return 0 - - -if __name__ == "__main__": - exit(main()) diff --git a/scripts/uploads/JSON_uploads_size.py b/scripts/uploads/JSON_uploads_size.py deleted file mode 100644 index 03796b30..00000000 --- a/scripts/uploads/JSON_uploads_size.py +++ /dev/null @@ -1,84 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -################### -# This file prints a JSON object of uploaded files sizes. -# Copyright (C) 2021, 2022 Maurice Lambert - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. - -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. - -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -################### - -""" -This tool run scripts and display the result in a Web Interface. - -This file prints a JSON object of uploaded files sizes. -""" - -__version__ = "1.0.0" -__author__ = "Maurice Lambert" -__author_email__ = "mauricelambert434@gmail.com" -__maintainer__ = "Maurice Lambert" -__maintainer_email__ = "mauricelambert434@gmail.com" -__description__ = """ -This tool run scripts and display the result in a Web Interface. - -This file prints a JSON object of uploaded files -""" -__license__ = "GPL-3.0 License" -__url__ = "https://github.com/mauricelambert/WebScripts" - -copyright = """ -WebScripts Copyright (C) 2021, 2022 Maurice Lambert -This program comes with ABSOLUTELY NO WARRANTY. -This is free software, and you are welcome to redistribute it -under certain conditions. -""" -license = __license__ -__copyright__ = copyright - -__all__ = ["main"] - -from modules.uploads_management import get_metadata -from json import dumps -from sys import exit - - -def main() -> int: - """ - This file prints a JSON object of uploaded files sizes. - """ - - print( - dumps( - { - name: { - "full size": metadata.full_size, - "last size": metadata.last_size, - "versions": metadata.version, - "modification": metadata.modification, - "creation (OS)": metadata.creation, - "creation (WebScripts)": metadata.webscripts_creation, - "Access": metadata.access, - } - for name, metadata in get_metadata().items() - }, - indent=4, - ) - ) - - return 0 - - -if __name__ == "__main__": - exit(main()) diff --git a/scripts/uploads/JSON_visible_files.py b/scripts/uploads/JSON_visible_files.py deleted file mode 100644 index b85dda49..00000000 --- a/scripts/uploads/JSON_visible_files.py +++ /dev/null @@ -1,89 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -################### -# This file prints a JSON object of uploaded files -# Copyright (C) 2021, 2022, 2023 Maurice Lambert - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. - -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. - -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -################### - -""" -This tool runs CLI scripts and displays output in a Web Interface. - -This file prints a JSON object of uploaded files. -""" - -__version__ = "1.0.1" -__author__ = "Maurice Lambert" -__author_email__ = "mauricelambert434@gmail.com" -__maintainer__ = "Maurice Lambert" -__maintainer_email__ = "mauricelambert434@gmail.com" -__description__ = """ -This tool runs CLI scripts and displays output in a Web Interface. - -This file prints a JSON object of uploaded files -""" -__license__ = "GPL-3.0 License" -__url__ = "https://github.com/mauricelambert/WebScripts" - -copyright = """ -WebScripts Copyright (C) 2021, 2022, 2023 Maurice Lambert -This program comes with ABSOLUTELY NO WARRANTY. -This is free software, and you are welcome to redistribute it -under certain conditions. -""" -license = __license__ -__copyright__ = copyright - -__all__ = ["main"] - -from modules.uploads_management import get_visible_files -from sys import exit, stderr, stdout -from json import dump - - -def main() -> int: - """ - This function prints the JSON object of uploaded files. - """ - - fields = [ - "name", - "read_permission", - "write_permission", - "delete_permission", - "user", - ] - - try: - files = get_visible_files() - except Exception as e: - print(f"{e.__class__.__name__}: {e}", file=stderr) - return 127 - - files = { - key: value - for file in files.values() - for key, value in file._asdict().items() - if key in fields - } - - dump(files, stdout) - - return 0 - - -if __name__ == "__main__": - exit(main()) diff --git a/scripts/uploads/api_get_all_files.py b/scripts/uploads/api_get_all_files.py deleted file mode 100644 index 3d92e56a..00000000 --- a/scripts/uploads/api_get_all_files.py +++ /dev/null @@ -1,79 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -################### -# This file prints a JSON object of uploaded files -# Copyright (C) 2021 Maurice Lambert - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. - -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. - -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -################### - -"""This tool run scripts and display the result in a Web Interface. - -This file prints a JSON object of uploaded files.""" - -__version__ = "0.0.1" -__author__ = "Maurice Lambert" -__author_email__ = "mauricelambert434@gmail.com" -__maintainer__ = "Maurice Lambert" -__maintainer_email__ = "mauricelambert434@gmail.com" -__description__ = """ -This tool run scripts and display the result in a Web Interface. - -This file prints a JSON object of uploaded files""" -__license__ = "GPL-3.0 License" -__url__ = "https://github.com/mauricelambert/WebScripts" - -copyright = """ -WebScripts Copyright (C) 2021 Maurice Lambert -This program comes with ABSOLUTELY NO WARRANTY. -This is free software, and you are welcome to redistribute it -under certain conditions. -""" -license = __license__ -__copyright__ = copyright - -__all__ = [] - -from modules.uploads_management import get_files -import json -import sys - - -def main() -> None: - """Print the JSON object of uploaded files.""" - - fields = [ - "name", - "read_permission", - "write_permission", - "delete_permission", - "user", - ] - - try: - files = { - file.name: {field: getattr(file, field) for field in fields} - for file in get_files() - } - except Exception as e: - print(f"{e.__class__.__name__}: {e}") - sys.exit(127) - - print(json.dumps(files, indent=4)) - - -if __name__ == "__main__": - main() - sys.exit(0) diff --git a/scripts/uploads/api_get_files.py b/scripts/uploads/api_get_files.py deleted file mode 100644 index cc7ec80a..00000000 --- a/scripts/uploads/api_get_files.py +++ /dev/null @@ -1,83 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -################### -# This file prints a JSON object of uploaded files -# Copyright (C) 2021 Maurice Lambert - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. - -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. - -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -################### - -"""This tool run scripts and display the result in a Web Interface. - -This file prints a JSON object of uploaded files.""" - -__version__ = "0.0.1" -__author__ = "Maurice Lambert" -__author_email__ = "mauricelambert434@gmail.com" -__maintainer__ = "Maurice Lambert" -__maintainer_email__ = "mauricelambert434@gmail.com" -__description__ = """ -This tool run scripts and display the result in a Web Interface. - -This file prints a JSON object of uploaded files""" -__license__ = "GPL-3.0 License" -__url__ = "https://github.com/mauricelambert/WebScripts" - -copyright = """ -WebScripts Copyright (C) 2021 Maurice Lambert -This program comes with ABSOLUTELY NO WARRANTY. -This is free software, and you are welcome to redistribute it -under certain conditions. -""" -license = __license__ -__copyright__ = copyright - -__all__ = [] - -from modules.uploads_management import get_visible_files -import json -import sys - - -def main() -> None: - """Print the JSON object of uploaded files.""" - - fields = [ - "name", - "read_permission", - "write_permission", - "delete_permission", - "user", - ] - - try: - files = get_visible_files() - except Exception as e: - print(f"{e.__class__.__name__}: {e}") - sys.exit(127) - - files = { - key: value - for file in files.values() - for key, value in file._asdict().items() - if key in fields - } - - print(json.dumps(files, indent=4)) - - -if __name__ == "__main__": - main() - sys.exit(0) diff --git a/scripts/uploads/api_get_history.py b/scripts/uploads/api_get_history.py deleted file mode 100644 index 88fae482..00000000 --- a/scripts/uploads/api_get_history.py +++ /dev/null @@ -1,81 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -################### -# This file prints a JSON objects of uploaded file versions -# Copyright (C) 2021, 2022 Maurice Lambert - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. - -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. - -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -################### - -""" -This tool run scripts and display the result in a Web Interface. - -This file prints a JSON objects of uploaded file versions. -""" - -__version__ = "1.0.0" -__author__ = "Maurice Lambert" -__author_email__ = "mauricelambert434@gmail.com" -__maintainer__ = "Maurice Lambert" -__maintainer_email__ = "mauricelambert434@gmail.com" -__description__ = """ -This tool run scripts and display the result in a Web Interface. - -This file prints a JSON object of uploaded file versions""" -__license__ = "GPL-3.0 License" -__url__ = "https://github.com/mauricelambert/WebScripts" - -copyright = """ -WebScripts Copyright (C) 2021, 2022 Maurice Lambert -This program comes with ABSOLUTELY NO WARRANTY. -This is free software, and you are welcome to redistribute it -under certain conditions. -""" -license = __license__ -__copyright__ = copyright - -__all__ = [] - -from modules.uploads_management import get_file -from sys import exit, argv, stderr -from json import dumps - - -def main() -> int: - """ - Print the JSON objects of file history. - """ - - if len(argv) != 2: - print("USAGE: api_get_history.py [FILENAME required string]") - return 1 - - filename = argv[1] - - try: - files, counter = get_file(filename) - except Exception as e: - print(f"{e.__class__.__name__}: {e}", file=stderr) - return 127 - - versions = [file._asdict() for file in files] - - print(dumps(versions, indent=4)) - - return 0 - - -if __name__ == "__main__": - exit(main()) diff --git a/scripts/uploads/delete_file.py b/scripts/uploads/delete_file.py deleted file mode 100644 index 61d18a58..00000000 --- a/scripts/uploads/delete_file.py +++ /dev/null @@ -1,73 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -################### -# This file deletes an uploaded file on a WebScripts Server -# Copyright (C) 2021, 2022, 2023 Maurice Lambert - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. - -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. - -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -################### - -"""# This tool runs CLI scripts and displays output in a Web Interface. - -This file deletes an uploaded file on a WebScripts Server.""" - -__version__ = "0.0.1" -__author__ = "Maurice Lambert" -__author_email__ = "mauricelambert434@gmail.com" -__maintainer__ = "Maurice Lambert" -__maintainer_email__ = "mauricelambert434@gmail.com" -__description__ = """ -This tool runs CLI scripts and displays output in a Web Interface. - -This file deletes an uploaded file on a WebScripts Server""" -__license__ = "GPL-3.0 License" -__url__ = "https://github.com/mauricelambert/WebScripts" - -copyright = """ -WebScripts Copyright (C) 2021, 2022, 2023 Maurice Lambert -This program comes with ABSOLUTELY NO WARRANTY. -This is free software, and you are welcome to redistribute it -under certain conditions. -""" -license = __license__ -__copyright__ = copyright - -__all__ = [] - -from modules.uploads_management import delete_file -import sys - - -def main() -> None: - """This function uploads a file on a WebScripts Server.""" - - if len(sys.argv) != 2: - print("USAGE: delete_file.py [FILENAME required string]") - sys.exit(1) - - filename = sys.argv[1] - - try: - upload = delete_file(filename) - except Exception as e: - print(f"{e.__class__.__name__}: {e}") - sys.exit(127) - - print(f"Deleted file:\n\t - Name: {upload.name}") - - -if __name__ == "__main__": - main() - sys.exit(0) diff --git a/scripts/uploads/download_all_files.py b/scripts/uploads/download_all_files.py deleted file mode 100644 index 3e2399c8..00000000 --- a/scripts/uploads/download_all_files.py +++ /dev/null @@ -1,110 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -################### -# This file prints a HTML link to download a file -# Copyright (C) 2021, 2022, 2023, 2024 Maurice Lambert - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. - -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. - -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -################### - -""" -This tool runs CLI scripts and displays output in a Web Interface. - -This file prints a HTML link to download a file. -""" - -__version__ = "1.0.1" -__author__ = "Maurice Lambert" -__author_email__ = "mauricelambert434@gmail.com" -__maintainer__ = "Maurice Lambert" -__maintainer_email__ = "mauricelambert434@gmail.com" -__description__ = """ -This tool runs CLI scripts and displays output in a Web Interface. - -This file prints a HTML link to download a file""" -__license__ = "GPL-3.0 License" -__url__ = "https://github.com/mauricelambert/WebScripts" - -copyright = """ -WebScripts Copyright (C) 2021, 2022, 2023, 2024 Maurice Lambert -This program comes with ABSOLUTELY NO WARRANTY. -This is free software, and you are welcome to redistribute it -under certain conditions. -""" -license = __license__ -__copyright__ = copyright - -__all__ = [] - -# from modules.uploads_management import get_file_content -from sys import argv, exit, stderr -from urllib.parse import quote -from html import escape - - -def main() -> int: - """ - Print the HTML link to download the file or - exit with an error code. - """ - - if len(argv) != 3: - print( - "USAGE: get_file.py [TYPE required string] " - '[FILENAME required string]\n\t TYPE must be "ID" or "name"', - file=stderr, - ) - return 1 - - _, type_, identifier = argv - type_ = type_.lower() - - if type_ == "id": - print( - f""" - - Click here to download the file - - """ - ) - elif type_ == "name": - print( - f""" - - Click here to download the {escape(identifier)} - - """ - ) - else: - print('ERROR: TYPE must be "ID" or "name"', file=stderr) - return 2 - - # try: - # data, filename = get_file_content(name=name, id_=id_) - # except Exception as e: - # print(escape(f"{e.__class__.__name__}: {e}"), file=stderr) - # exit(127) - - # print( - # f'Click here to download ' - # f"{escape(filename)}" - # ) - - return 0 - - -if __name__ == "__main__": - exit(main()) diff --git a/scripts/uploads/download_filename.py b/scripts/uploads/download_filename.py deleted file mode 100644 index af996936..00000000 --- a/scripts/uploads/download_filename.py +++ /dev/null @@ -1,112 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -################### -# This file prints a HTML link to download a file -# Copyright (C) 2021, 2022, 2023, 2024 Maurice Lambert - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. - -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. - -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -################### - -""" -This tool runs CLI scripts and displays output in a Web Interface. - -This file prints a HTML link to download a file. -""" - -__version__ = "1.0.1" -__author__ = "Maurice Lambert" -__author_email__ = "mauricelambert434@gmail.com" -__maintainer__ = "Maurice Lambert" -__maintainer_email__ = "mauricelambert434@gmail.com" -__description__ = """ -This tool runs CLI scripts and displays output in a Web Interface. - -This file prints a HTML link to download a file -""" -__license__ = "GPL-3.0 License" -__url__ = "https://github.com/mauricelambert/WebScripts" - -copyright = """ -WebScripts Copyright (C) 2021, 2022, 2023 Maurice Lambert -This program comes with ABSOLUTELY NO WARRANTY. -This is free software, and you are welcome to redistribute it -under certain conditions. -""" -license = __license__ -__copyright__ = copyright - -__all__ = [] - -from modules.uploads_management import ( - # read_file, - get_file, - check_permissions, - get_user, -) -from sys import exit, argv, stderr -from urllib.parse import quote -from html import escape - - -def main() -> None: - """ - This function prints the HTML link to download the file. - """ - - if len(argv) != 2: - print("USAGES: get_file.py [FILENAME required string]", file=stderr) - return 1 - - filename = argv[1] - - uploads, counter = get_file(filename) - - if len(uploads) == 0: - print( - f"FileNotFoundError: No such file or directory: {filename}", - file=stderr, - ) - return 2 - - file = uploads[-1] - owner = get_user() - try: - check_permissions(file, owner, "read") - except Exception as e: - print(f"{e.__class__.__name__}: {e}", file=stderr) - return 127 - - print( - f'' - f"Click here to download {escape(filename)}" - ) - - return 0 - - # try: - # data = read_file(filename) - # except Exception as e: - # print(html.escape(f"{e.__class__.__name__}: {e}")) - # sys.exit(127) - - # print( - # f'Click here to download ' - # f"{html.escape(filename)}" - # ) - - -if __name__ == "__main__": - exit(main()) diff --git a/scripts/uploads/get_all_files.py b/scripts/uploads/get_all_files.py deleted file mode 100644 index 58c522b9..00000000 --- a/scripts/uploads/get_all_files.py +++ /dev/null @@ -1,96 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -################### -# This file prints a HTML table of uploaded files -# Copyright (C) 2021 Maurice Lambert - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. - -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. - -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -################### - -"""This tool run scripts and display the result in a Web Interface. - -This file prints a HTML table of uploaded files.""" - -__version__ = "0.1.0" -__author__ = "Maurice Lambert" -__author_email__ = "mauricelambert434@gmail.com" -__maintainer__ = "Maurice Lambert" -__maintainer_email__ = "mauricelambert434@gmail.com" -__description__ = """ -This tool run scripts and display the result in a Web Interface. - -This file prints a HTML table of uploaded files""" -__license__ = "GPL-3.0 License" -__url__ = "https://github.com/mauricelambert/WebScripts" - -copyright = """ -WebScripts Copyright (C) 2021 Maurice Lambert -This program comes with ABSOLUTELY NO WARRANTY. -This is free software, and you are welcome to redistribute it -under certain conditions. -""" -license = __license__ -__copyright__ = copyright - -__all__ = [] - -from modules.uploads_management import get_files -from time import localtime, strftime -from urllib.parse import quote -import html -import sys - - -def main() -> None: - """Print the HTML table of uploaded files.""" - - fields = [ - "name", - "timestamp", - "read_permission", - "write_permission", - "delete_permission", - "user", - ] - print(f"") - - try: - files = { - file.name: {field: getattr(file, field) for field in fields} - for file in get_files() - } - except Exception as e: - print(html.escape(f"{e.__class__.__name__}: {e}")) - sys.exit(127) - - for file in files.values(): - print( - f'" - f"" - f"" - f"" - ) - - print("
{''.join(fields)}
' - f'{html.escape(file["name"])}' - + strftime( - "%Y-%m-%d %H:%M:%S", localtime(float(file["timestamp"])) - ) - + f"{html.escape(file['read_permission'])}{html.escape(file['write_permission'])}{html.escape(file['delete_permission'])}{html.escape(file['user'])}
") - - -if __name__ == "__main__": - main() - sys.exit(0) diff --git a/scripts/uploads/get_any_file.py b/scripts/uploads/get_any_file.py deleted file mode 100644 index 8f7a3ffc..00000000 --- a/scripts/uploads/get_any_file.py +++ /dev/null @@ -1,110 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -################### -# This file prints a HTML link to download a file -# Copyright (C) 2021, 2022, 2024 Maurice Lambert - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. - -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. - -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -################### - -""" -This tool runs scripts and display the result in a Web Interface. - -This file prints a HTML link to download a file. -""" - -__version__ = "1.0.1" -__author__ = "Maurice Lambert" -__author_email__ = "mauricelambert434@gmail.com" -__maintainer__ = "Maurice Lambert" -__maintainer_email__ = "mauricelambert434@gmail.com" -__description__ = """ -This tool run scripts and display the result in a Web Interface. - -This file prints a HTML link to download a file""" -__license__ = "GPL-3.0 License" -__url__ = "https://github.com/mauricelambert/WebScripts" - -copyright = """ -WebScripts Copyright (C) 2021, 2022, 2024 Maurice Lambert -This program comes with ABSOLUTELY NO WARRANTY. -This is free software, and you are welcome to redistribute it -under certain conditions. -""" -license = __license__ -__copyright__ = copyright - -__all__ = [] - -# from modules.uploads_management import get_file_content -from sys import argv, exit, stderr -from urllib.parse import quote -from html import escape - - -def main() -> int: - """ - Print the HTML link to download the file or - exit with an error code. - """ - - if len(argv) != 3: - print( - "USAGE: get_file.py [TYPE required string] " - '[FILENAME required string]\n\t TYPE must be "ID" or "name"', - file=stderr, - ) - return 1 - - _, type_, identifier = argv - type_ = type_.lower() - - if type_ == "id": - print( - f""" - - Click here to download the file - - """ - ) - elif type_ == "name": - print( - f""" - - Click here to download the {escape(identifier)} - - """ - ) - else: - print('ERROR: TYPE must be "ID" or "name"', file=stderr) - return 2 - - # try: - # data, filename = get_file_content(name=name, id_=id_) - # except Exception as e: - # print(escape(f"{e.__class__.__name__}: {e}"), file=stderr) - # exit(127) - - # print( - # f'Click here to download ' - # f"{escape(filename)}" - # ) - - return 0 - - -if __name__ == "__main__": - exit(main()) diff --git a/scripts/uploads/get_file.py b/scripts/uploads/get_file.py deleted file mode 100644 index a55e25b5..00000000 --- a/scripts/uploads/get_file.py +++ /dev/null @@ -1,109 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -################### -# This file prints a HTML link to download a file -# Copyright (C) 2021, 2024 Maurice Lambert - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. - -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. - -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -################### - -""" -This tool runs scripts and display the result in a Web Interface. - -This file prints a HTML link to download a file. -""" - -__version__ = "0.2.1" -__author__ = "Maurice Lambert" -__author_email__ = "mauricelambert434@gmail.com" -__maintainer__ = "Maurice Lambert" -__maintainer_email__ = "mauricelambert434@gmail.com" -__description__ = """ -This tool run scripts and display the result in a Web Interface. - -This file prints a HTML link to download a file""" -__license__ = "GPL-3.0 License" -__url__ = "https://github.com/mauricelambert/WebScripts" - -copyright = """ -WebScripts Copyright (C) 2021, 2024 Maurice Lambert -This program comes with ABSOLUTELY NO WARRANTY. -This is free software, and you are welcome to redistribute it -under certain conditions. -""" -license = __license__ -__copyright__ = copyright - -__all__ = [] - -from modules.uploads_management import ( - # read_file, - get_file, - check_permissions, - get_user, -) -from urllib.parse import quote -import html -import sys - - -def main() -> None: - """Print the HTML link to download the file or - exit with an error code.""" - - if len(sys.argv) != 2: - print("USAGE: get_file.py [FILENAME required string]") - sys.exit(1) - - filename = sys.argv[1] - - uploads, counter = get_file(filename) - - if len(uploads) == 0: - print( - f"FileNotFoundError: No such file or directory: {filename}", - file=sys.stderr, - ) - sys.exit(2) - - file = uploads[-1] - owner = get_user() - try: - check_permissions(file, owner, "read") - except Exception as e: - print(f"{e.__class__.__name__}: {e}", file=sys.stderr) - sys.exit(127) - - print( - f'' - f"Click here to download {html.escape(filename)}" - ) - - # try: - # data = read_file(filename) - # except Exception as e: - # print(html.escape(f"{e.__class__.__name__}: {e}")) - # sys.exit(127) - - # print( - # f'Click here to download ' - # f"{html.escape(filename)}" - # ) - - -if __name__ == "__main__": - main() - sys.exit(0) diff --git a/scripts/uploads/get_files.py b/scripts/uploads/get_files.py deleted file mode 100644 index 2bc54d5a..00000000 --- a/scripts/uploads/get_files.py +++ /dev/null @@ -1,91 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -################### -# This file prints a HTML table of uploaded files -# Copyright (C) 2021 Maurice Lambert - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. - -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. - -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -################### - -"""This tool run scripts and display the result in a Web Interface. - -This file prints a HTML table of uploaded files.""" - -__version__ = "0.1.0" -__author__ = "Maurice Lambert" -__author_email__ = "mauricelambert434@gmail.com" -__maintainer__ = "Maurice Lambert" -__maintainer_email__ = "mauricelambert434@gmail.com" -__description__ = """ -This tool run scripts and display the result in a Web Interface. - -This file prints a HTML table of uploaded files""" -__license__ = "GPL-3.0 License" -__url__ = "https://github.com/mauricelambert/WebScripts" - -copyright = """ -WebScripts Copyright (C) 2021 Maurice Lambert -This program comes with ABSOLUTELY NO WARRANTY. -This is free software, and you are welcome to redistribute it -under certain conditions. -""" -license = __license__ -__copyright__ = copyright - -__all__ = [] - -from modules.uploads_management import get_visible_files -from time import localtime, strftime -from urllib.parse import quote -import html -import sys - - -def main() -> None: - """Print the HTML table of uploaded files.""" - - fields = [ - "name", - "time", - "read_permission", - "write_permission", - "delete_permission", - "user", - ] - print(f"") - - try: - files = get_visible_files() - except Exception as e: - print(html.escape(f"{e.__class__.__name__}: {e}")) - sys.exit(127) - - for file in files.values(): - print( - f'" - f"" - f"" - f"" - ) - - print("
{''.join(fields)}
' - f"{html.escape(file.name)}" - + strftime("%Y-%m-%d %H:%M:%S", localtime(float(file.timestamp))) - + f"{html.escape(file.read_permission)}{html.escape(file.write_permission)}{html.escape(file.delete_permission)}{html.escape(file.user)}
") - - -if __name__ == "__main__": - main() - sys.exit(0) diff --git a/scripts/uploads/get_history.py b/scripts/uploads/get_history.py deleted file mode 100644 index 973c8bce..00000000 --- a/scripts/uploads/get_history.py +++ /dev/null @@ -1,113 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -################### -# This file prints a HTML table of uploaded file versions -# Copyright (C) 2021, 2022, 2024 Maurice Lambert - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. - -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. - -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -################### - -""" -This tool runs scripts and display the result in a Web Interface. - -This file prints a HTML table of uploaded file versions. -""" - -__version__ = "1.0.1" -__author__ = "Maurice Lambert" -__author_email__ = "mauricelambert434@gmail.com" -__maintainer__ = "Maurice Lambert" -__maintainer_email__ = "mauricelambert434@gmail.com" -__description__ = """ -This tool run scripts and display the result in a Web Interface. - -This file prints a HTML table of uploaded file versions. -""" -__license__ = "GPL-3.0 License" -__url__ = "https://github.com/mauricelambert/WebScripts" - -copyright = """ -WebScripts Copyright (C) 2021, 2022, 2024 Maurice Lambert -This program comes with ABSOLUTELY NO WARRANTY. -This is free software, and you are welcome to redistribute it -under certain conditions. -""" -license = __license__ -__copyright__ = copyright - -__all__ = [] - -from modules.uploads_management import get_file -from time import localtime, strftime -from sys import argv, exit, stderr -from urllib.parse import quote -from html import escape - - -def main() -> int: - """ - Print the HTML table of file history. - """ - - if len(argv) != 2: - print("USAGE: get_history.py [FILENAME required string]") - return 1 - - filename = argv[1] - - fields = [ - "ID", - "name", - "read_permission", - "write_permission", - "delete_permission", - "hidden", - "is_deleted", - "is_binary", - "timestamp", - "user", - "version", - ] - print( - f"" - ) - - try: - files, counter = get_file(filename) - except Exception as e: - print(escape(f"{e.__class__.__name__}: {e}"), file=stderr) - return 127 - - for file in files: - file = file._replace( - ID='{escape(file.ID)}', - timestamp=strftime( - "%Y-%m-%d %H:%M:%S", localtime(float(file.timestamp)) - ), - ) - print( - f'" - ) - - print("
{''.join(fields)}" - "
{"".join([escape(x) for x in file])}' - "
") - - return 0 - - -if __name__ == "__main__": - exit(main()) diff --git a/scripts/uploads/modules/uploads_management.py b/scripts/uploads/modules/uploads_management.py deleted file mode 100644 index 463df7a2..00000000 --- a/scripts/uploads/modules/uploads_management.py +++ /dev/null @@ -1,562 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -################### -# This file implement some functions to manage uploads on WebScripts -# Copyright (C) 2021, 2022, 2023 Maurice Lambert - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. - -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. - -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -################### - -""" -This tool runs CLI scripts and displays output in a Web Interface. - -This file implement some functions to manage uploads on WebScripts. -""" - -__version__ = "1.0.0" -__author__ = "Maurice Lambert" -__author_email__ = "mauricelambert434@gmail.com" -__maintainer__ = "Maurice Lambert" -__maintainer_email__ = "mauricelambert434@gmail.com" -__description__ = """ -This tool runs CLI scripts and displays output in a Web Interface. - -This file implement some functions to manage uploads on WebScripts -""" -__license__ = "GPL-3.0 License" -__url__ = "https://github.com/mauricelambert/WebScripts" - -copyright = """ -WebScripts Copyright (C) 2021, 2022, 2023 Maurice Lambert -This program comes with ABSOLUTELY NO WARRANTY. -This is free software, and you are welcome to redistribute it -under certain conditions. -""" -license = __license__ -__copyright__ = copyright - -__all__ = [ - "Upload", - "get_file", - "read_file", - "write_file", - "get_reader", - "delete_file", - "get_metadata", - "FileMetadata", - "UploadedFile", - "get_file_content", - "get_visible_files", -] - -from gzip import open as gzip, decompress as ungzip -from lzma import open as xz, decompress as unxz -from os.path import join, split, splitext, exists -from typing import Tuple, List, TypeVar, Dict -from collections import namedtuple, Counter -from time import time, strftime, localtime -from csv import reader, writer, QUOTE_ALL -from os import environ, stat, stat_result -from base64 import b64encode, b64decode -from collections.abc import Iterator -from collections import defaultdict -from _io import _TextIOBase -from html import escape -from json import loads -from math import ceil - -Upload = namedtuple( - "Upload", - [ - "ID", - "name", - "read_permission", - "write_permission", - "delete_permission", - "hidden", - "is_deleted", - "is_binary", - "no_compression", - "timestamp", - "user", - "version", - ], -) -FILE = "uploads.csv" -DIRECTORY = environ["WEBSCRIPTS_DATA_PATH"] -FILES_DIRECTORY = "uploads" -User = TypeVar("User") -Data = TypeVar("Data", str, bytes) - - -class FileMetadata: - """ - This class implements file metadata for - uploaded files. - """ - - def __init__(self): - self.full_size = 0 - self.version = 0 - - def add(self, stat: stat_result, timestamp: float): - """ - This function add a version to file metadata. - """ - - size = stat.st_size - self.version += 1 - self.last_size = size - self.full_size = size - - self.webscripts_creation = timestamp - - self.modification = stat.st_mtime - self.creation = stat.st_ctime - self.access = stat.st_atime - - -class UploadedFile: - """ - This class implements the file type for - uploaded files. - """ - - def __init__( - self, - name: str, - read_access: int, - write_access: int, - delete_access: int, - hidden: bool, - binary: bool, - no_compression: bool, - with_access: bool = True, - ): - owner = get_user() - uploads, counter = get_file(name) - - if with_access and len(uploads) != 0: - file = uploads[-1] - check_permissions(file, owner, "write") - - timestamp = time() - - upload = self.upload = anti_XSS( - Upload( - str(sum(counter.values())), - name, - str(read_access), - str(write_access), - str(delete_access), - "hidden" if hidden else "visible", - "exist", - "binary" if binary else "text", - "no_compression" if no_compression else "", - str(timestamp), - owner["name"], - str(counter[name]), - ) - ) - - write_action(upload) - - filename = get_real_file_name(name, timestamp) - - compression = not no_compression - if compression and not binary: - self.file = gzip(filename, "wb") - elif compression and binary: - self.file = xz(filename, "wb") - else: - self.file = open(filename, "wb") - - def __getattr__(self, attr: str): - if attr in dir(self): - return object.__getattr__(self, attr) - return getattr(self.file, attr) - - def __del__(self, *args, **kwargs): - return self.file.__del__(*args, **kwargs) - - def __enter__(self, *args, **kwargs): - return self.file.__enter__(*args, **kwargs) - - def __exit__(self, *args, **kwargs): - return self.file.__exit__(*args, **kwargs) - - def __iter__(self, *args, **kwargs): - yield from self.file.__iter__(*args, **kwargs) - - def __next__(self, *args, **kwargs): - yield from self.file.__next__(*args, **kwargs) - - -def write_file( - data: str, - name: str, - read_access: int, - write_access: int, - delete_access: int, - hidden: bool, - binary: bool, - no_compression: bool, - is_b64: bool, - with_access: bool = True, -) -> Upload: - """ - This function uploads a file. - """ - - if is_b64: - data = b64decode(data.encode()) - else: - data = data.encode("utf-8") - - file = UploadedFile( - name, - read_access, - write_access, - delete_access, - hidden, - binary, - no_compression, - with_access, - ) - file.write(data) - file.close() - - return file.upload - - -def upgrade_uploads() -> None: - """ - This function upgrade the database. - - Add default no_compression ("", empty string) - """ - - uploads = [] - first = False - filepath = join(DIRECTORY, FILE) - uploads_add = uploads.append - - with open(filepath, newline="") as csvfile: - csvreader = reader(csvfile, quoting=QUOTE_ALL) - - for row in csvreader: - if len(row) == 11: - if first: - row.insert(8, "") - else: - row.insert(8, "no_compression") - first = True - - uploads_add(row) - - with open(filepath, "w", newline="") as csvfile: - csvwriter = writer(csvfile, quoting=QUOTE_ALL) - writerow = csvwriter.writerow - - [writerow(upload) for upload in uploads] - - -def anti_XSS(named_tuple: namedtuple) -> namedtuple: - """ - This function returns a namedtuple - without HTML special characters. - """ - - new = {} - for attribut, value in named_tuple._asdict().items(): - new[attribut] = escape(value) - return named_tuple.__class__(**new) - - -def get_files() -> Iterator[Upload]: - """ - This function build Uploads from database. - """ - - yield from map( - Upload._make, - reader( - open(join(DIRECTORY, FILE), "r", newline=""), - quoting=QUOTE_ALL, - ), - ) - - -def get_metadata() -> Dict[str, FileMetadata]: - """ - This function returns metadata of - each uploaded files and versions. - """ - - files = defaultdict(FileMetadata) - - for file in get_files(): - name = file.name - timestamp = float(file.timestamp) - filename = get_real_file_name(name, timestamp) - files[name].add(stat(filename), timestamp) - - return files - - -def get_visible_files() -> Iterator[Upload]: - """ - This function return upload if not hidden. - """ - - files = {} - - for file in get_files(): - file = anti_XSS(file) - if file.hidden != "hidden" and file.is_deleted != "deleted": - files[file.name] = file - elif ( - file.hidden == "hidden" or file.is_deleted == "deleted" - ) and file.name in files.keys(): - del files[file.name] - - return files - - -def unicode_to_bytes(string: str) -> bytes: - """ - This function return bytes from unicode strings.""" - - data = b"" - for char in string: - char = ord(char) - data += char.to_bytes(ceil(char.bit_length() / 8), "big") - - return data - - -def delete_file(name: str) -> Upload: - """ - This function delete an uploaded file. - """ - - uploads, counter = get_file(name) - - if len(uploads) == 0: - raise FileNotFoundError(f"No such file or directory: {name}.") - - file = uploads[-1] - owner = get_user() - check_permissions(file, owner, "delete") - timestamp = time() - - upload = anti_XSS( - Upload( - str(sum(counter.values())), - file.name, - file.read_permission, - file.write_permission, - file.delete_permission, - file.hidden, - "deleted", - file.is_binary, - file.no_compression, - str(timestamp), - owner["name"], - file.version, - ) - ) - write_action(upload) - - return upload - - -def write_action(upload: Upload) -> None: - """ - This function write a new line in CSV database. - """ - - for string in upload: - if not string.isprintable(): - raise ValueError(f"Strings must be printable: '{string}' is not.") - - with open(join(DIRECTORY, FILE), "a", newline="") as csvfile: - csv_writer = writer(csvfile, quoting=QUOTE_ALL) - csv_writer.writerow(anti_XSS(upload)) - - -def check_permissions(file: Upload, owner: User, attr: str) -> None: - """ - This function raises a PermissionError if - the user does not have write permission. - """ - - permission = int(getattr(file, f"{attr}_permission")) - - if ( - attr == "write" - and permission > max(owner["groups"]) - and file.is_deleted != "deleted" - ): - raise PermissionError( - f"To write on this file ({file.name}) a group ID greater" - f" than {permission} is required." - ) - elif (attr == "read" or attr == "delete") and permission > max( - owner["groups"] - ): - raise PermissionError( - f"To {attr} this file ({file.name}) a group ID " - f"greater than {permission} is required." - ) - elif (attr == "read" or attr == "delete") and file.is_deleted == "deleted": - raise FileNotFoundError(f"No such file or directory: {file.name}.") - - -def get_user() -> User: - """ - This function return the user. - """ - - return loads(environ["USER"]) - - -def read_file(name: str) -> str: - """ - This function check permission and - return a base64 of the file content. - """ - - uploads, counter = get_file(name) - - if len(uploads) == 0: - raise FileNotFoundError(f"No such file or directory: {name}.") - - file = uploads[-1] - owner = get_user() - check_permissions(file, owner, "read") - - return get_content(file) - - -def get_reader(file: Upload) -> _TextIOBase: - """ - This function returns a reader - of the uploaded file. - """ - - compression = not file.no_compression - filename = get_real_file_name(file.name, float(file.timestamp)) - - if compression and file.is_binary == "text": - reader = ungzip(filename) - elif compression and file.is_binary == "binary": - reader = unxz(filename) - else: - reader = open(filename, "rb") - - return reader - - -def get_content(file: Upload) -> str: - """ - This function read, decompress and - encode/decode the file content. - """ - - data = reader.read() - reader.close() - - return b64encode(data).decode() - - -def get_file_content(name: str = None, id_: str = None) -> Tuple[str, str]: - """ - This function return a base64 of the file - content and the filename (without check permissions). - - If id_ and name arguments are None this function return (None, None). - - Using a name this function return the last versions of the file content. - Using an ID this function return the version of this ID. - """ - - if id_ is not None: - error_description = f'using "{id_}" as ID' - - elif name is not None: - error_description = f'using "{name}" as name' - - else: - return None, None - - uploads, counter = get_file(name, id_=id_) - - if len(uploads) == 0: - raise FileNotFoundError( - f"No such file or directory: {error_description}." - ) - - file = uploads[-1] - filename = get_real_file_name(file.name, float(file.timestamp)) - - if not exists(filename): - raise FileNotFoundError( - f"No such file or directory: {error_description}." - ) - - only_filename = split(file.name)[1] - return get_content(file), only_filename - - -def get_file(name: str, id_: str = None) -> Tuple[List[Upload], Counter]: - """ - This function return the history of a file. - - If name is None, this function get Upload by ID. - """ - - versions = [] - counter = Counter() - - for file in get_files(): - file = anti_XSS(file) - counter[file.name] += 1 - if file.name == name or (name is None and id_ == file.ID): - versions.append(file) - - return versions, counter - - -def get_real_file_name(filename: str, timestamp: float) -> str: - """ - This function return the real filename of a file. - """ - - filename, extension = splitext(split(filename)[1]) - - return join( - DIRECTORY, - FILES_DIRECTORY, - f"{filename}_" - f"{strftime('%y%m%d_%H%M%S', localtime(timestamp))}{extension}", - ) - - -try: - for upload in get_files(): - break -except TypeError: - upgrade_uploads() diff --git a/scripts/uploads/upload_file.py b/scripts/uploads/upload_file.py deleted file mode 100644 index c0298ab4..00000000 --- a/scripts/uploads/upload_file.py +++ /dev/null @@ -1,135 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -################### -# This file uploads a file on a WebScripts Server -# Copyright (C) 2021, 2022, 2023 Maurice Lambert - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. - -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. - -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -################### - -"""# This tool runs CLI scripts and displays output in a Web Interface. - -This file uploads a file on a WebScripts Server.""" - -__version__ = "0.0.2" -__author__ = "Maurice Lambert" -__author_email__ = "mauricelambert434@gmail.com" -__maintainer__ = "Maurice Lambert" -__maintainer_email__ = "mauricelambert434@gmail.com" -__description__ = """ -This tool runs CLI scripts and displays output in a Web Interface. - -This file uploads a file on a WebScripts Server""" -__license__ = "GPL-3.0 License" -__url__ = "https://github.com/mauricelambert/WebScripts" - -copyright = """ -WebScripts Copyright (C) 2021, 2022, 2023 Maurice Lambert -This program comes with ABSOLUTELY NO WARRANTY. -This is free software, and you are welcome to redistribute it -under certain conditions. -""" -license = __license__ -__copyright__ = copyright - -__all__ = [] - -from modules.uploads_management import write_file, get_user -from argparse import ArgumentParser, Namespace -import sys - - -def parse_args() -> Namespace: - """This function parse command line arguments.""" - - owner = get_user() - groupID = max(owner["groups"]) - - parser = ArgumentParser() - parser.add_argument("name", help="The filename of uploaded file.") - parser.add_argument( - "--read-permission", - "-r", - help="Minimum Group ID to read the file", - type=int, - default=groupID, - ) - parser.add_argument( - "--write-permission", - "-w", - help="Minimum Group ID to write the file", - type=int, - default=groupID, - ) - parser.add_argument( - "--delete-permission", - "-d", - help="Minimum Group ID to delete the file", - type=int, - default=groupID, - ) - parser.add_argument( - "--hidden", - "-H", - help="Hidden file (unlisted in Web Interface)", - action="store_true", - default=False, - ) - parser.add_argument( - "--binary", - "-b", - help="Upload a binary file (ZIP, executable...)", - action="store_true", - default=False, - ) - parser.add_argument( - "--no-compression", - "-c", - help="Do not compress the file.", - action="store_true", - default=False, - ) - parser.add_argument( - "--is-b64", - "-i", - help="Using base64 to upload the file", - action="store_true", - default=False, - ) - return parser.parse_args() - - -def main() -> None: - """This function uploads a file on a WebScripts Server.""" - - arguments = parse_args() - - try: - upload = write_file(sys.stdin.read(), *arguments.__dict__.values()) - except Exception as e: - print(f"{e.__class__.__name__}: {e}") - sys.exit(127) - - print( - f"UPLOADED FILE:\n\t - Name: {upload.name}" - f"\n\t - Permissions: r={upload.read_permission};" - f"w={upload.write_permission};d={upload.delete_permission};" - f"\n\t - {upload.hidden}" - ) - - -if __name__ == "__main__": - main() - sys.exit(0) diff --git a/static/css/webscripts_index_style.css b/static/css/webscripts_index_style.css deleted file mode 100644 index ece68463..00000000 --- a/static/css/webscripts_index_style.css +++ /dev/null @@ -1,37 +0,0 @@ -/* - - Style for index page - Copyright (C) 2021, 2022 Maurice Lambert - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - -*/ - -.category_content { - width: 15%; - padding: 1%%; - display: inline-block; - margin-bottom: 2%; - margin-right: 2%; - border-radius: 4px; - transition: 0.3s; -} - -.category_content:hover { - background-color: rgb(51, 51, 51); -} - -.category_content.light:hover { - background-color: rgb(221, 221, 221); -} diff --git a/static/css/webscripts_script_style.css b/static/css/webscripts_script_style.css deleted file mode 100644 index d8c023cf..00000000 --- a/static/css/webscripts_script_style.css +++ /dev/null @@ -1,256 +0,0 @@ -/* - - Style for script.html - Copyright (C) 2021, 2022 Maurice Lambert - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - -*/ - -input, select, button { - padding: 1%; - width: 100%; - background-color: #222222; - color: #dd8a12; - transition: 0.5s; - font-size: 100%; - border: 2px solid #f0a83f; - border-radius: 10px; - /* float:right; */ -} - -button { - border: 1px solid #f0a83f; -} - -input:hover, select:hover, button:hover { - background-color: #333333; -} - -code, pre { - padding: 0.3%; -} - -table, tr, td { - padding: 1rem; - margin: 1%; -} - -table { - border-spacing: 0.2rem; - margin: 0px; -} - -td { - border: 1px solid #dd8a12; -} - -select option:checked { - background: #895100 -webkit-linear-gradient(bottom, #895100 0%, #895100 100%); - color: #FFFFFF; -} - -select option:hover { - background: #895100 -webkit-linear-gradient(bottom, #895100 0%, #895100 100%); - color: #FFFFFF; -} - -select.light option.light:checked { - background: #615CB9 -webkit-linear-gradient(bottom, #615CB9 0%, #615CB9 100%); - color: #CCCCCC; -} - -select.light option.light:hover { - background: #615CB9 -webkit-linear-gradient(bottom, #615CB9 0%, #615CB9 100%); - color: #CCCCCC; -} - -select.light { - color: #27219a; - border-color: #100B60; -} - -option.light { - color: #27219a; -} - -progress { - background-color: #222222; - width: 98%; - fill: #895100; - border: 1px solid #ffc26a; - lighting-color: #895100; - border-radius: 10px; - margin: 2%; -} - -progress::-moz-progress-bar { - background: #895100; - border-radius: 10px; -} - -progress::-webkit-progress-value { - background: #895100; - border-radius: 10px; -} - -progress.light { - background-color: #CCCCCC; - fill: #615CB9; - border: 1px solid #0F0B60; - lighting-color: #615CB9; -} - -progress.light::-moz-progress-bar { - background: #615CB9; -} - -progress.light::-webkit-progress-value { - background: #615CB9; -} - -/*input[type=checkbox]:before { - background: #895100; - color: #000000; -} - -input[type=checkbox]:checked:after { - background: #000000; - color: #895100; -}*/ - -/* ::-ms-check { - color: red; - background: black; - padding: 1em; -} */ - -.argument_container { - width: 60%; - margin: 1%; -} - -.submit { - background-color: #f0a83f; - color: #895100; - border-color: #895100; - width: 100%; - padding: 2%; -} - -input.submit.light { - color: #DDDDDD; - background-color: #615CB9; -} - -input.submit.light:hover { - background-color: #27219a; -} - -input:disabled { - background-color: #895100; - color: #333333; -} - -input.light:disabled { - background-color: #0F0B60; - color: #CCCCCC; -} - -.submit_position { - margin: 0 auto; - width: 30%; -} - -.row { - width:100%; - display:block; -} - -.script_presentation { - width: 17%; -} - -.webscripts_search_table { - width: 40%; - margin: 1%; - padding: 0.3%; - border: 0px; -} - -.webscripts_search_table:hover { - border: 1px solid #895100; -} - -.webscripts_column_select { - color: #DDAD12; -} - -.webscripts_column_select.selected { - color: #2AA177; -} - -.webscripts_column_select.unselected { - color: #F0783F; -} - -.webscripts_column_select.light { - color: #531895; -} - -.webscripts_column_select.light.selected { - color: #4AB18F; -} - -.webscripts_column_select.light.unselected { - color: #F07B3F; -} - -#script_presentation { - margin: 1%; -} - -#script_outputs { - margin-top: 1%; -} - -#advanced_button_arguments { - width: 10%; - float: right; -} - -#advanced_arguments { - display: none; -} - -#print_advanced { - background-color: #895100; - border-radius: 5px; - border: 2px solid #dd8a12; - color: #222222; -} - -#print_advanced:hover { - background-color: #b16b05; -} - -.light#print_advanced { - color: #433DA7; - background-color: #CCCCCC; - border: 2px solid #433DA7; -} - -.light#print_advanced:hover { - background-color: #AAAAAA; -} \ No newline at end of file diff --git a/static/css/webscripts_style.css b/static/css/webscripts_style.css deleted file mode 100644 index 5840f264..00000000 --- a/static/css/webscripts_style.css +++ /dev/null @@ -1,537 +0,0 @@ -/* - - Style for WebScript pages. - Copyright (C) 2021, 2022 Maurice Lambert - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - -*/ - -/* - * Colors: - - ------------------------------ - - DARK - - ------------------------------ - - #ffc26a: Orange clair; - #f0a83f: Orange mi-clair; - #dd8a12 rgb(221, 138, 18) : Orange; - #b16b05: Orange mi-foncé; - #895100: Orange foncé; - - #DDAD12: Jaune; - #896900: Jaune foncé; - #FFDC6A: Jaune clair; - - #222222: Gris foncé; - #333333: Gris clair; - - #2AA177: Vert mi-clair; - #F0783F: Rouge mi-clair; - - ------------------------------ - - LIGHT - - ------------------------------ - - #615CB9: Bleu clair; - #433DA7: Bleu mi-clair; - #27219a: Bleu; - #100B60: Bleu mi-foncé; - #0F0B60: Bleu foncé; - - #531895: violet; - #3F0D78: violet mi-foncé; - #DDAD13: orange; - #B18906: orange mi-foncé; - - #DDDDDD: Gris clair; - #CCCCCC: Gris foncé; - - #4AB18F: Vert mi-clair; - #F07B3F: Rouge mi-clair; -*/ - -* { - margin: 0; - padding: 0; -} - -body, html { - margin: 0%; - padding: 0%; - width: 100%; - height: 100%; - /*overflow: hidden;*/ - font-family : Arial, Helvetica, "Liberation Sans", FreeSans, sans-serif; - /*overflow-y:scroll;*/ - background-color: #222222; -} - -header, footer { - font-style: italic; - padding: 2%; -} - -header { - /*position: fixed;*/ -} - -pre { - tab-size: 4; - background-color: black; - color: white; - font-family: "Courier", "Lucida Console", "Consolas", "sans-serif"; - font-size: 100%; -} - -a { - color: #895100; -} - -button { - padding: 1%; - width: 100%; - background-color: #222222; - color: #dd8a12; - transition: 0.5s; - font-size: 100%; - border: 2px solid #f0a83f; - border-radius: 10px; -} - -.notification { - width: 17%; - display: inline-block; - color: #DDAD12; - text-align: center; - border: 1px solid #333333; - border-radius: 4px; -} - -.notification_user { - font-style: italic; - color: #FFDC6A; - font-weight: 400; -} - -.notification_close { - padding: 1%; - width: auto; - font-size: 10px; - border: 1px solid #333333; - float: right; - border-radius: 4px; -} - -.notification_close:hover { - background-color: #333333; -} - -.border { - color: #ffc26a; -} - -.description { - font-style: italic; - font-size: 80%; -} - -.inline { - display: inline-block; -} - -.center { - text-align: center; -} - -.header_border { - width: 20%; -} - -.header_center { - margin-right: 20%; - margin-left: 20%; -} - -.header_container { - display: inline-block; - width: 20%; -} - -.search_result { - background-color: #2F2F2F; - border: 1px solid #181818; - padding: 1%; - transition: 0.5s; -} - -.search_result_description { - color: #b16b05; -} - -.webscripts_menu_button { - margin: 0%; - padding: 0%; - width: 49.5%; - color: #FFDC6A; - border-radius: 0px; - text-align: center; - border: 0px solid #333333; - background-color: #222222; - transition: 0.2s; - display: inline-block; -} - -.webscripts_menu_values { - margin: 0%; - padding: 0%; - width: 49.5%; - display: none; -} - -div.category { - background-color: rgba(34, 34, 34, 0.7); - border-radius: 5px; - margin: 3px; - padding: 3px; - /*width: auto;*/ -} - -div.category.light { - background-color: rgba(204, 204, 204, 0.7); - border-radius: 3px; - margin: 2px; -} - -.search_result_description.light { - color: #100B60; -} - -.border.light, button.light { - color: #0F0B60; -} - -.webscripts_menu_button.light { - background-color: #DDDDDD; - border: 0px solid #CCCCCC; - color: #3F0D78; -} - -.search_result:hover { - background-color: #222222; -} - -.search_result.light { - background-color: rgb(221, 221, 221); - color: #100B60; - border-color: #615CB9; -} - -.search_result.light:hover { - background-color: #CCCCCC; -} - -.list.footer { - font-size: 80%; - margin-bottom: 1%; -} - -.webscripts_menu_button:hover { - background-color: #333333; - color: #896900; - border: 1px solid #896900; -} - -.webscripts_menu_button.light:hover { - background-color: #CCCCCC; - color: #B18906; - border: 1px solid #B18906; -} - -html.light, body.light, button.light, input.light, select.light { - background-color: #CCCCCC; -} - -input.light:hover, select.light:hover, button.light:hover { - background-color: #DDDDDD; -} - -a.light { - color: #615CB9; -} - -button.light { - border-color: #0F0B60; -} - -#webscripts_history { - float: right; -} - -#webscripts_menu_options { - margin: 0%; - width: 100%; - padding: 0%; -} - -#webscripts_menu { - padding: 0%; - border-radius: 10px; - background-color: #222222; - border: 1px solid #333333; -} - -#webscripts_menu.light { - background-color: #DDDDDD; - border: 1px solid #CCCCCC; -} - -#webscripts_menu_button_left { - border-radius: 10px 0px 0px 10px; -} - -#webscripts_menu_button_right { - border-radius: 0px 10px 10px 0px; -} - -#webscripts_content { - background-color: #333333; - color: #dd8a12; - padding: 2%; - width: 87%; - display: inline-block; - border: 1px solid #b16b05; - border-radius: 10px; - margin: 4%; -} - -#webscripts_title { - font-size: 120%; -} - -#webscripts_description { - font-size: 80%; -} - -#webscripts_header_image_position { - display: inline-block; - margin-left: 58%; -} - -#webscripts_search_bar_size { - /* background-image: radial-gradient(#222222 10%, transparent 3%); */ - display: inline-block; - padding-top: 2%; - padding-bottom: 2%; - padding-left: 10%; - padding-right: 10%; - position: fixed; - width: 38%; -} - -#webscripts_search_bar { - padding: 2%; - width: 100%; - background-color: #333; - color: rgb(255, 194, 106); - border: 1px solid #895100; - border-radius: 3px; -} - -#webscripts_search_bar.light { - background-color: rgb(221, 221, 221); - color: #100B60; - border-color: #615CB9; -} - -#search_result { - display: none; -} - -#prevent_no_javascript { - background-color: red; - padding: 2%; - margin: 3%; - font-weight: bold; - font-size: 120%; -} - -#webscripts_header_canvas_image { - animation: rotate 5s 2; -} - -#webscripts_content.light { - background-color: #DDDDDD; -} - -#bar.light { - background-color: #615CB9; - color: #0F0B60; -} - -/* -#webscripts_search_bar_size.light { - background-image: radial-gradient(#CCCCCC 10%, transparent); -} -*/ - -#webscripts_content.light, input.light, td.light { - color: #100B60; - border-color: #100B60; -} - -::-moz-selection { /* Code for Firefox */ - color: #333333; - background: #f0a83f; -} - -::selection { - color: #333333; - background: #f0a83f; -} - -.toast { - visibility: hidden; - margin: auto; - background-color: #666; - color: #fff; - text-align: center; - border-radius: 5px; - padding: 16px; - position: fixed; - left: 47%; - bottom: 7%; -} - -.toast.show { - visibility: visible; - -webkit-animation: fadein 0.5s, fadeout 0.5s 2.5s; - animation: fadein 0.5s, fadeout 0.5s 2.5s; -} - -@-webkit-keyframes fadein { - from {bottom: 0; opacity: 0;} - to {bottom: 7%; opacity: 1;} -} - -@keyframes fadein { - from {bottom: 0; opacity: 0;} - to {bottom: 7%; opacity: 1;} -} - -@-webkit-keyframes fadeout { - from {bottom: 30px; opacity: 1;} - to {bottom: 0; opacity: 0;} -} - -@keyframes fadeout { - from {bottom: 30px; opacity: 1;} - to {bottom: 0; opacity: 0;} -} - -@media (prefers-color-scheme: dark) { - html.default_theme, body.default_theme, button.default_theme, input.default_theme, select.default_theme { - background-color: #222222; - } - - input.default_theme:hover, select.default_theme:hover, button.default_theme:hover { - background-color: #333333; - } - - a.default_theme { - color: #895100; - } - - #webscripts_content.default_theme { - background-color: #333333; - } - - .border.default_theme { - color: #ffc26a; - } - - button.default_theme, input.default_theme, select.default_theme { - color: #dd8a12; - border-color: #f0a83f; - } - - #webscripts_content.default_theme, td.default_theme { - color: #dd8a12; - border-color: #b16b05; - } - - #bar.default_theme { - background-color: #895100; - color: #ffc26a; - } - - td.default_theme { - border-color: #dd8a12; - } -} - -@media (prefers-color-scheme: light) { - html.default_theme, body.default_theme, button.default_theme, input.default_theme, select.default_theme { - background-color: #CCCCCC; - } - - input.default_theme:hover, select.default_theme:hover, button.default_theme:hover { - background-color: #DDDDDD; - } - - a.default_theme { - color: #615CB9; - } - - #webscripts_content.default_theme { - background-color: #DDDDDD; - } - - .border.default_theme, button.default_theme { - color: #0F0B60; - } - - #bar.default_theme { - background-color: #615CB9; - color: #0F0B60; - } - - button.default_theme { - border-color: #0F0B60; - } - - #webscripts_content.default_theme, input.default_theme, td.default_theme { - color: #100B60; - border-color: #100B60; - } -} - -@keyframes rotate { - 0% { - transform: rotate(0deg) - } - 20% { - transform: rotate(72deg) - } - 40% { - transform: rotate(144deg) - } - 60% { - transform: rotate(288deg) - } - 80% { - transform: rotate(360deg) - } -} \ No newline at end of file diff --git a/static/html/Configurations.html b/static/html/Configurations.html deleted file mode 100644 index 4ccdc495..00000000 --- a/static/html/Configurations.html +++ /dev/null @@ -1,97 +0,0 @@ - -Python: module Configurations - - - - - -
 
- 
Configurations (version 0.0.1)
index
configurations.py
-

This tool runs CLI scripts and displays output in a Web Interface.

-This file implements a debug mode module to changes configurations
-and reload modules.

-

- - - - - -
 
-Classes
       
-
builtins.object -
-
-
Reload -
-
-
-

- - - - - -
 
-class Reload(builtins.object)
    Methods defined here:
-
arguments(environ: os._Environ, user: ~User, server: ~Server, name: str, arguments: Dict[str, Dict[str, str]], inputs: List[str], csrf_token: str = None) -> Tuple[str, Dict[str, str], str]
This function reloads a argument configuration.
- -
module(environ: os._Environ, user: ~User, server: ~Server, name: str, arguments: Dict[str, Dict[str, str]], inputs: List[str], csrf_token: str = None) -> Tuple[str, Dict[str, str], str]
This function reloads a module.
- -
modules(environ: os._Environ, user: ~User, server: ~Server, name: str, arguments: Dict[str, Dict[str, str]], inputs: List[str], csrf_token: str = None) -> Tuple[str, Dict[str, str], str]
This function adds new modules.
- -
scripts(environ: os._Environ, user: ~User, server: ~Server, name: str, arguments: Dict[str, Dict[str, str]], inputs: List[str], csrf_token: str = None) -> Tuple[str, Dict[str, str], str]
This function reloads a script configuration.
- -
server(environ: os._Environ, user: ~User, server: ~Server, name: str, arguments: Dict[str, Dict[str, str]], inputs: List[str], csrf_token: str = None) -> Tuple[str, Dict[str, str], str]
This function reloads the configuration.
- -
web(environ: os._Environ, user: ~User, server: ~Server, name: str, arguments: Dict[str, Dict[str, str]], inputs: List[str], csrf_token: str = None) -> Tuple[str, Dict[str, str], str]
This function reloads web files (JS, CSS and HTML).
- -
-Data descriptors defined here:
-
__dict__
-
dictionary for instance variables (if defined)
-
-
__weakref__
-
list of weak references to the object (if defined)
-
-
-Data and other attributes defined here:
-
__annotations__ = {'accept_types': typing.Tuple[type], 'html': <class 'str'>, 'simple_types': typing.Tuple[type]}
- -
accept_types = (<class 'list'>, <class 'NoneType'>, <class 'str'>, <class 'bool'>, <class 'int'>, <class 'float'>)
- -
html = '\n <html><head><title>WebScripts Server co... }}\n </script></footer></body></html>\n '
- -
simple_types = (<class 'NoneType'>, <class 'str'>, <class 'bool'>, <class 'int'>, <class 'float'>)
- -

- - - - - -
 
-Data
       Dict = typing.Dict
-List = typing.List
-Server = ~Server
-Tuple = typing.Tuple
-User = ~User
-__author_email__ = 'mauricelambert434@gmail.com'
-__copyright__ = '\nWebScripts Copyright (C) 2021, 2022, 2023 Mau...ome to redistribute it\nunder certain conditions.\n'
-__description__ = '\nThis tool runs CLI scripts and displays output ...le to changes configurations\nand reload modules.\n'
-__license__ = 'GPL-3.0 License'
-__maintainer__ = 'Maurice Lambert'
-__maintainer_email__ = 'mauricelambert434@gmail.com'
-__url__ = 'https://github.com/mauricelambert/WebScripts'
-copyright = '\nWebScripts Copyright (C) 2021, 2022, 2023 Mau...ome to redistribute it\nunder certain conditions.\n'
-license = 'GPL-3.0 License'

- - - - - -
 
-Author
       Maurice Lambert
- \ No newline at end of file diff --git a/static/html/Errors.html b/static/html/Errors.html deleted file mode 100644 index 3b2922f4..00000000 --- a/static/html/Errors.html +++ /dev/null @@ -1,515 +0,0 @@ - -Python: module Errors - - - - - -
 
- 
Errors (version 0.0.2)
index
errors.py
-

This tool runs CLI scripts and displays output in a Web Interface.

-This file contains WebScripts exception classes.

-

- - - - - -
 
-Classes
       
-
builtins.Exception(builtins.BaseException) -
-
-
WebScriptsError -
-
-
MissingAttributesError -
WebScriptsArgumentError -
WebScriptsConfigurationError -
-
-
ScriptConfigurationError -
WebScriptsConfigurationTypeError -
-
-
WebScriptsSecurityError -
-
-
-
-
-

- - - - - - - -
 
-class MissingAttributesError(WebScriptsError)
   To raise Missing Attributes Error.
 
 
Method resolution order:
-
MissingAttributesError
-
WebScriptsError
-
builtins.Exception
-
builtins.BaseException
-
builtins.object
-
-
-Data descriptors inherited from WebScriptsError:
-
__weakref__
-
list of weak references to the object (if defined)
-
-
-Methods inherited from builtins.Exception:
-
__init__(self, /, *args, **kwargs)
Initialize self.  See help(type(self)) for accurate signature.
- -
-Static methods inherited from builtins.Exception:
-
__new__(*args, **kwargs) from builtins.type
Create and return a new object.  See help(type) for accurate signature.
- -
-Methods inherited from builtins.BaseException:
-
__delattr__(self, name, /)
Implement delattr(self, name).
- -
__getattribute__(self, name, /)
Return getattr(self, name).
- -
__reduce__(...)
Helper for pickle.
- -
__repr__(self, /)
Return repr(self).
- -
__setattr__(self, name, value, /)
Implement setattr(self, name, value).
- -
__setstate__(...)
- -
__str__(self, /)
Return str(self).
- -
with_traceback(...)
Exception.with_traceback(tb) --
-set self.__traceback__ to tb and return self.
- -
-Data descriptors inherited from builtins.BaseException:
-
__cause__
-
exception cause
-
-
__context__
-
exception context
-
-
__dict__
-
-
__suppress_context__
-
-
__traceback__
-
-
args
-
-

- - - - - - - -
 
-class ScriptConfigurationError(WebScriptsConfigurationError)
   To raise Script Configuration Error.
 
 
Method resolution order:
-
ScriptConfigurationError
-
WebScriptsConfigurationError
-
WebScriptsError
-
builtins.Exception
-
builtins.BaseException
-
builtins.object
-
-
-Data descriptors inherited from WebScriptsError:
-
__weakref__
-
list of weak references to the object (if defined)
-
-
-Methods inherited from builtins.Exception:
-
__init__(self, /, *args, **kwargs)
Initialize self.  See help(type(self)) for accurate signature.
- -
-Static methods inherited from builtins.Exception:
-
__new__(*args, **kwargs) from builtins.type
Create and return a new object.  See help(type) for accurate signature.
- -
-Methods inherited from builtins.BaseException:
-
__delattr__(self, name, /)
Implement delattr(self, name).
- -
__getattribute__(self, name, /)
Return getattr(self, name).
- -
__reduce__(...)
Helper for pickle.
- -
__repr__(self, /)
Return repr(self).
- -
__setattr__(self, name, value, /)
Implement setattr(self, name, value).
- -
__setstate__(...)
- -
__str__(self, /)
Return str(self).
- -
with_traceback(...)
Exception.with_traceback(tb) --
-set self.__traceback__ to tb and return self.
- -
-Data descriptors inherited from builtins.BaseException:
-
__cause__
-
exception cause
-
-
__context__
-
exception context
-
-
__dict__
-
-
__suppress_context__
-
-
__traceback__
-
-
args
-
-

- - - - - - - -
 
-class WebScriptsArgumentError(WebScriptsError)
   To raise Argument Error.
 
 
Method resolution order:
-
WebScriptsArgumentError
-
WebScriptsError
-
builtins.Exception
-
builtins.BaseException
-
builtins.object
-
-
-Data descriptors inherited from WebScriptsError:
-
__weakref__
-
list of weak references to the object (if defined)
-
-
-Methods inherited from builtins.Exception:
-
__init__(self, /, *args, **kwargs)
Initialize self.  See help(type(self)) for accurate signature.
- -
-Static methods inherited from builtins.Exception:
-
__new__(*args, **kwargs) from builtins.type
Create and return a new object.  See help(type) for accurate signature.
- -
-Methods inherited from builtins.BaseException:
-
__delattr__(self, name, /)
Implement delattr(self, name).
- -
__getattribute__(self, name, /)
Return getattr(self, name).
- -
__reduce__(...)
Helper for pickle.
- -
__repr__(self, /)
Return repr(self).
- -
__setattr__(self, name, value, /)
Implement setattr(self, name, value).
- -
__setstate__(...)
- -
__str__(self, /)
Return str(self).
- -
with_traceback(...)
Exception.with_traceback(tb) --
-set self.__traceback__ to tb and return self.
- -
-Data descriptors inherited from builtins.BaseException:
-
__cause__
-
exception cause
-
-
__context__
-
exception context
-
-
__dict__
-
-
__suppress_context__
-
-
__traceback__
-
-
args
-
-

- - - - - - - -
 
-class WebScriptsConfigurationError(WebScriptsError)
   To raise Configuration Error.
 
 
Method resolution order:
-
WebScriptsConfigurationError
-
WebScriptsError
-
builtins.Exception
-
builtins.BaseException
-
builtins.object
-
-
-Data descriptors inherited from WebScriptsError:
-
__weakref__
-
list of weak references to the object (if defined)
-
-
-Methods inherited from builtins.Exception:
-
__init__(self, /, *args, **kwargs)
Initialize self.  See help(type(self)) for accurate signature.
- -
-Static methods inherited from builtins.Exception:
-
__new__(*args, **kwargs) from builtins.type
Create and return a new object.  See help(type) for accurate signature.
- -
-Methods inherited from builtins.BaseException:
-
__delattr__(self, name, /)
Implement delattr(self, name).
- -
__getattribute__(self, name, /)
Return getattr(self, name).
- -
__reduce__(...)
Helper for pickle.
- -
__repr__(self, /)
Return repr(self).
- -
__setattr__(self, name, value, /)
Implement setattr(self, name, value).
- -
__setstate__(...)
- -
__str__(self, /)
Return str(self).
- -
with_traceback(...)
Exception.with_traceback(tb) --
-set self.__traceback__ to tb and return self.
- -
-Data descriptors inherited from builtins.BaseException:
-
__cause__
-
exception cause
-
-
__context__
-
exception context
-
-
__dict__
-
-
__suppress_context__
-
-
__traceback__
-
-
args
-
-

- - - - - - - -
 
-class WebScriptsConfigurationTypeError(WebScriptsConfigurationError)
   To raise Configuration Error.
 
 
Method resolution order:
-
WebScriptsConfigurationTypeError
-
WebScriptsConfigurationError
-
WebScriptsError
-
builtins.Exception
-
builtins.BaseException
-
builtins.object
-
-
-Data descriptors inherited from WebScriptsError:
-
__weakref__
-
list of weak references to the object (if defined)
-
-
-Methods inherited from builtins.Exception:
-
__init__(self, /, *args, **kwargs)
Initialize self.  See help(type(self)) for accurate signature.
- -
-Static methods inherited from builtins.Exception:
-
__new__(*args, **kwargs) from builtins.type
Create and return a new object.  See help(type) for accurate signature.
- -
-Methods inherited from builtins.BaseException:
-
__delattr__(self, name, /)
Implement delattr(self, name).
- -
__getattribute__(self, name, /)
Return getattr(self, name).
- -
__reduce__(...)
Helper for pickle.
- -
__repr__(self, /)
Return repr(self).
- -
__setattr__(self, name, value, /)
Implement setattr(self, name, value).
- -
__setstate__(...)
- -
__str__(self, /)
Return str(self).
- -
with_traceback(...)
Exception.with_traceback(tb) --
-set self.__traceback__ to tb and return self.
- -
-Data descriptors inherited from builtins.BaseException:
-
__cause__
-
exception cause
-
-
__context__
-
exception context
-
-
__dict__
-
-
__suppress_context__
-
-
__traceback__
-
-
args
-
-

- - - - - - - -
 
-class WebScriptsError(builtins.Exception)
   To raise WebScripts errors
 
 
Method resolution order:
-
WebScriptsError
-
builtins.Exception
-
builtins.BaseException
-
builtins.object
-
-
-Data descriptors defined here:
-
__weakref__
-
list of weak references to the object (if defined)
-
-
-Methods inherited from builtins.Exception:
-
__init__(self, /, *args, **kwargs)
Initialize self.  See help(type(self)) for accurate signature.
- -
-Static methods inherited from builtins.Exception:
-
__new__(*args, **kwargs) from builtins.type
Create and return a new object.  See help(type) for accurate signature.
- -
-Methods inherited from builtins.BaseException:
-
__delattr__(self, name, /)
Implement delattr(self, name).
- -
__getattribute__(self, name, /)
Return getattr(self, name).
- -
__reduce__(...)
Helper for pickle.
- -
__repr__(self, /)
Return repr(self).
- -
__setattr__(self, name, value, /)
Implement setattr(self, name, value).
- -
__setstate__(...)
- -
__str__(self, /)
Return str(self).
- -
with_traceback(...)
Exception.with_traceback(tb) --
-set self.__traceback__ to tb and return self.
- -
-Data descriptors inherited from builtins.BaseException:
-
__cause__
-
exception cause
-
-
__context__
-
exception context
-
-
__dict__
-
-
__suppress_context__
-
-
__traceback__
-
-
args
-
-

- - - - - - - -
 
-class WebScriptsSecurityError(WebScriptsError)
   To raise Security Error in WebScripts services.
 
 
Method resolution order:
-
WebScriptsSecurityError
-
WebScriptsError
-
builtins.Exception
-
builtins.BaseException
-
builtins.object
-
-
-Data descriptors inherited from WebScriptsError:
-
__weakref__
-
list of weak references to the object (if defined)
-
-
-Methods inherited from builtins.Exception:
-
__init__(self, /, *args, **kwargs)
Initialize self.  See help(type(self)) for accurate signature.
- -
-Static methods inherited from builtins.Exception:
-
__new__(*args, **kwargs) from builtins.type
Create and return a new object.  See help(type) for accurate signature.
- -
-Methods inherited from builtins.BaseException:
-
__delattr__(self, name, /)
Implement delattr(self, name).
- -
__getattribute__(self, name, /)
Return getattr(self, name).
- -
__reduce__(...)
Helper for pickle.
- -
__repr__(self, /)
Return repr(self).
- -
__setattr__(self, name, value, /)
Implement setattr(self, name, value).
- -
__setstate__(...)
- -
__str__(self, /)
Return str(self).
- -
with_traceback(...)
Exception.with_traceback(tb) --
-set self.__traceback__ to tb and return self.
- -
-Data descriptors inherited from builtins.BaseException:
-
__cause__
-
exception cause
-
-
__context__
-
exception context
-
-
__dict__
-
-
__suppress_context__
-
-
__traceback__
-
-
args
-
-

- - - - - -
 
-Data
       __all__ = ['WebScriptsError', 'WebScriptsConfigurationError', 'WebScriptsArgumentError', 'ScriptConfigurationError', 'MissingAttributesError', 'WebScriptsConfigurationTypeError', 'WebScriptsSecurityError']
-__author_email__ = 'mauricelambert434@gmail.com'
-__copyright__ = '\nWebScripts Copyright (C) 2021, 2022, 2023 Mau...ome to redistribute it\nunder certain conditions.\n'
-__description__ = '\nThis tool runs CLI scripts and displays output ...This file contains WebScripts exception classes.\n'
-__license__ = 'GPL-3.0 License'
-__maintainer__ = 'Maurice Lambert'
-__maintainer_email__ = 'mauricelambert434@gmail.com'
-__url__ = 'https://github.com/mauricelambert/WebScripts'

- - - - - -
 
-Author
       Maurice Lambert
- \ No newline at end of file diff --git a/static/html/JsonRpc.html b/static/html/JsonRpc.html deleted file mode 100644 index c7de29f7..00000000 --- a/static/html/JsonRpc.html +++ /dev/null @@ -1,86 +0,0 @@ - -Python: module JsonRpc - - - - - -
 
- 
JsonRpc (version 1.0.0)
index
jsonrpc.py
-

This tool runs CLI scripts and displays output in a Web Interface.

-This module implements JSON RPC on WebScripts.

-

- - - - - -
 
-Classes
       
-
builtins.object -
-
-
JsonRpc -
-
-
-

- - - - - - - -
 
-class JsonRpc(builtins.object)
   JsonRpc(environ: os._Environ, user: ~User, server: ~Server, filename: str, calls: Union[List[Dict[str, Any]], Dict[str, Any]], inputs: List[str], csrf_token: str = None) -&gt; Tuple[str, Dict[str, str], str]

-This class implements JSON RPC for the WebScripts Server.
 
 Class methods defined here:
-
execute_call(json: Dict[str, Any]) -> Dict[str, Any] from builtins.type
This function performs a JSON RPC call.
- -
register_function(function: collections.abc.Callable, name: str = None) from builtins.type
This function adds a new function in the JSON RPC calls.
- -
-Static methods defined here:
-
__new__(cls: type, environ: os._Environ, user: ~User, server: ~Server, filename: str, calls: Union[List[Dict[str, Any]], Dict[str, Any]], inputs: List[str], csrf_token: str = None) -> Tuple[str, Dict[str, str], str]
Create and return a new object.  See help(type) for accurate signature.
- -
-Data descriptors defined here:
-
__dict__
-
dictionary for instance variables (if defined)
-
-
__weakref__
-
list of weak references to the object (if defined)
-
-
-Data and other attributes defined here:
-
__annotations__ = {'functions': typing.Dict[str, collections.abc.Callable]}
- -
functions = {'call': <function test_call>, 'test_args_dict': <function test_argument_dict>, 'test_argument_list': <function test_argument_list>}
- -

- - - - - -
 
-Data
       __all__ = ['JsonRpc']
-__author_email__ = 'mauricelambert434@gmail.com'
-__copyright__ = '\nWebScripts Copyright (C) 2021, 2022, 2023 Mau...ome to redistribute it\nunder certain conditions.\n'
-__description__ = '\nThis tool runs CLI scripts and displays output ...\n\nThis module implements JSON RPC on WebScripts.\n'
-__license__ = 'GPL-3.0 License'
-__maintainer__ = 'Maurice Lambert'
-__maintainer_email__ = 'mauricelambert434@gmail.com'
-__url__ = 'https://github.com/mauricelambert/WebScripts'

- - - - - -
 
-Author
       Maurice Lambert
- \ No newline at end of file diff --git a/static/html/Pages.html b/static/html/Pages.html deleted file mode 100644 index 11bd503d..00000000 --- a/static/html/Pages.html +++ /dev/null @@ -1,111 +0,0 @@ - -Python: module Pages - - - - - -
 
- 
Pages (version 2.0.4)
index
pages.py
-

This tool runs CLI scripts and displays output in a Web Interface.

-This file implement Pages (Api and Web system), script execution and right
-system.

-

- - - - - -
 
-Modules
       
ntpath
-

- - - - - -
 
-Classes
       
-
builtins.object -
-
-
Pages -
-
-
-

- - - - - - - -
 
-class Pages(builtins.object)
   This class implement Web Pages for WebScripts server.
 
 Methods defined here:
-
__call__(self, environ: os._Environ, user: commons.User, server: ~Server, filename: str, command: List[str], inputs: List[str], csrf_token: str = None) -> Tuple[str, Dict[str, str], bytes]
A redirect page (Error code 301, javascript redirect and redirect
-title) to /web/ or /api/.
- -
auth(self, environ: os._Environ, user: commons.User, server: ~Server, filename: str, command: List[str], inputs: List[str], csrf_token: str = None) -> Tuple[str, Dict[str, str], bytes]
This function return check auth and return headers, error and page.
- -
js(self, environ: os._Environ, user: commons.User, server: ~Server, filename: str, command: List[str], inputs: List[str], csrf_token: str = None) -> Tuple[str, Dict[str, str], Union[bytes, Iterable[bytes]]]
This function get Javascripts Scripts and send it.
- -
reload(self, environ: os._Environ, user: commons.User, server: ~Server, filename: str, command: List[str], inputs: List[str], csrf_token: str = None) -> Tuple[str, Dict[str, str], str]
This function is a simple URL to reload scripts
-(useful for developpers to add/modify a script).
- -
static(self, environ: os._Environ, user: commons.User, server: ~Server, filename: str, command: List[str], inputs: List[str], csrf_token: str = None) -> Tuple[str, Dict[str, str], Union[bytes, Iterable[bytes]]]
This function get static file and send it.
- -
-Static methods defined here:
-
webfile(files: Dict[str, commons.CallableFile], user: commons.User, filename: str, base: str) -> Tuple[str, Dict[str, str], Union[bytes, Iterable[bytes]]]
This function builds response for Web files.
- -
-Data descriptors defined here:
-
__dict__
-
dictionary for instance variables (if defined)
-
-
__weakref__
-
list of weak references to the object (if defined)
-
-
-Data and other attributes defined here:
-
__annotations__ = {'ip_blacklist': typing.Dict[str, commons.Blacklist], 'js_paths': typing.Dict[str, commons.CallableFile], 'packages': <class 'utils.DefaultNamespace'>, 'processes': typing.Dict[str, Pages.Process], 'scripts': typing.Dict[str, commons.ScriptConfig], 'sessions': typing.Dict[int, commons.Session], 'statics_paths': typing.Dict[str, commons.CallableFile], 'user_blacklist': typing.Dict[str, commons.Blacklist]}
- -
api = <Pages.Api object>
- -
ip_blacklist = {}
- -
processes = {}
- -
sessions = {}
- -
user_blacklist = {}
- -
web = <Pages.Web object>
- -

- - - - - -
 
-Data
       __all__ = ['Pages']
-__author_email__ = 'mauricelambert434@gmail.com'
-__copyright__ = '\nWebScripts Copyright (C) 2021, 2022, 2023 Mau...ome to redistribute it\nunder certain conditions.\n'
-__description__ = '\nThis tool runs CLI scripts and displays output ... Web system), script execution and right\nsystem.\n'
-__license__ = 'GPL-3.0 License'
-__maintainer__ = 'Maurice Lambert'
-__maintainer_email__ = 'mauricelambert434@gmail.com'
-__url__ = 'https://github.com/mauricelambert/WebScripts'

- - - - - -
 
-Author
       Maurice Lambert
- \ No newline at end of file diff --git a/static/html/WebScripts.html b/static/html/WebScripts.html deleted file mode 100644 index 0b0fde63..00000000 --- a/static/html/WebScripts.html +++ /dev/null @@ -1,295 +0,0 @@ - -Python: module WebScripts - - - - - -
 
- 
WebScripts (version 1.0.7)
index
webscripts.py
-

This tool runs CLI scripts and displays output in a Web Interface.

-This file is the "main" file of this package (implements the main function,
-the Server class and the Configuration class).

-

- - - - - -
 
-Modules
       
logging
-
wsgiref.simple_server
-
sys
-

- - - - - -
 
-Classes
       
-
builtins.object -
-
-
Server -
-
-
utils.DefaultNamespace(types.SimpleNamespace) -
-
-
Configuration -
-
-
-

- - - - - - - -
 
-class Configuration(utils.DefaultNamespace)
   Configuration(required: List[str] = [], optional: List[str] = [], default: dict = {}, types: dict = {})

-This class build the configuration from dict(s) with
-configuration files and arguments.
 
 
Method resolution order:
-
Configuration
-
utils.DefaultNamespace
-
types.SimpleNamespace
-
builtins.object
-
-
-Methods defined here:
-
add_conf(self, **kwargs)
Add configurations from other configuration files found.
- -
-Data and other attributes defined here:
-
__defaults__ = {'accept_unauthenticated_user': True, 'accept_unknow_user': True, 'active_auth': False, 'auth_failures_to_blacklist': None, 'auth_script': None, 'blacklist_time': None, 'cgi_path': [], 'documentations_path': [], 'exclude_auth_pages': ['/api/', '/auth/', '/web/auth/'], 'exclude_auth_paths': ['/static/', '/js/'], ...}
- -
__optional__ = ('debug', 'security', 'active_auth', 'auth_script', 'accept_unknow_user', 'accept_unauthenticated_user', 'exclude_auth_paths', 'exclude_auth_pages', 'modules', 'modules_path', 'js_path', 'statics_path', 'documentations_path', 'scripts_path', 'json_scripts_config', 'ini_scripts_config', 'log_level', 'log_filename', 'log_level', 'log_format', ...)
- -
__required__ = ('interface', 'port')
- -
__types__ = {'accept_unauthenticated_user': <class 'bool'>, 'accept_unknow_user': <class 'bool'>, 'active_auth': <class 'bool'>, 'admin_adresses': <class 'list'>, 'admin_groups': typing.List[int], 'auth_failures_to_blacklist': <class 'int'>, 'blacklist_time': <class 'int'>, 'csrf_max_time': <class 'int'>, 'debug': <class 'bool'>, 'documentations_path': <class 'list'>, ...}
- -
-Methods inherited from utils.DefaultNamespace:
-
__getitem__(self, key: str)
Compatibility with dict.
- -
__init__(self, required: List[str] = [], optional: List[str] = [], default: dict = {}, types: dict = {})
Initialize self.  See help(type(self)) for accurate signature.
- -
build_type(self, attribut: str, value: Any, type_: type = None) -> None
This function builds type from configuration value.
- -
build_types(self) -> None
This function builds type from configuration values.
- -
check_required(self) -> None
This function checks required attributes
-if one of required attributes is missing this
-function raise MissingAttributesError.
- -
export_as_json(self, name: str = None) -> None
This function export namespace values (useful for debugging).
- -
get(self, key: str, default=None)
Compatibility with dict.
- -
get_dict(self) -> None
This function return a dict of attributes.
- -
get_missings(self) -> List[str]
This function checks required attributes
-and return a List[str] of missing required attributes.
- -
get_unexpecteds(self, log: bool = True) -> List[str]
This function return a List[str] of
-all attributes not in optional and
-required attributes.

-If log argument is True a Warning log message is
-write for all unexpected attributes.
- -
set_defaults(self) -> None
This function set defaults attribut with defaults values.
- -
update(self, **kwargs)
This function add/update attributes with **kwargs arguments.
- -
-Class methods inherited from utils.DefaultNamespace:
-
default_build(**kwargs) -> ~DefaultNamespace from builtins.type
Default build for DefaultNamespace (set defaults, add values,
-check requirements and unexpected values and build types).
- -
-Data descriptors inherited from utils.DefaultNamespace:
-
__weakref__
-
list of weak references to the object (if defined)
-
-
-Methods inherited from types.SimpleNamespace:
-
__delattr__(self, name, /)
Implement delattr(self, name).
- -
__eq__(self, value, /)
Return self==value.
- -
__ge__(self, value, /)
Return self>=value.
- -
__getattribute__(self, name, /)
Return getattr(self, name).
- -
__gt__(self, value, /)
Return self>value.
- -
__le__(self, value, /)
Return self<=value.
- -
__lt__(self, value, /)
Return self<value.
- -
__ne__(self, value, /)
Return self!=value.
- -
__reduce__(...)
Return state information for pickling
- -
__repr__(self, /)
Return repr(self).
- -
__setattr__(self, name, value, /)
Implement setattr(self, name, value).
- -
-Static methods inherited from types.SimpleNamespace:
-
__new__(*args, **kwargs) from builtins.type
Create and return a new object.  See help(type) for accurate signature.
- -
-Data descriptors inherited from types.SimpleNamespace:
-
__dict__
-
-
-Data and other attributes inherited from types.SimpleNamespace:
-
__hash__ = None
- -

- - - - - - - -
 
-class Server(builtins.object)
   Server(configuration: WebScripts.Configuration)

-This class implements the WebScripts server.
 
 Methods defined here:
-
__init__(self, configuration: WebScripts.Configuration)
Initialize self.  See help(type(self)) for accurate signature.
- -
add_module_or_package(self) -> None
This function add packages and modules to build custom page.
- -
add_paths(self) -> None
This function add js, static and scripts paths.
- -
app(self, environ_: os._Environ, respond: method) -> List[bytes]
This function get function page,
-return content page, catch errors and
-return HTTP errors.
- -
check_auth(self, environ: os._Environ) -> Tuple[commons.User, bool]
This function check if user is authenticated and blacklisted.
- -
check_blacklist(self, user: commons.User, ip: str) -> bool
This function checks that the IP and the
-username are not in the blacklist.
- -
get_URLs(self) -> List[str]
This function return a list of urls (scripts, documentation...)
-and the start of the URL of custom packages.
- -
get_function_page(self, path: str, filename: str) -> Tuple[~FunctionOrNone, str, bool]
This function find function from URL path.
-If the function is a WebScripts built-in function,
-return the function, filename and True. Else return the
-function, filename and False.
- -
get_session(self, cookies: List[str], ip: str) -> commons.User
This function return User from cookies.
- -
page_400(self, environ: os._Environ, user: commons.User, filename: str, method: str, respond: method)
This function return error 400 web page.
- -
page_401(self, environ: os._Environ, user: commons.User, filename: str, error_description: str, respond: method)
This function return error 401 web page.
- -
page_403(self, environ: os._Environ, user: commons.User, filename: str, error_description: str, respond: method)
This function return error 403 web page.
- -
page_404(self, environ: os._Environ, user: commons.User, filename: str, url: str, respond: method)
This function return error 404 web page.
- -
page_406(self, environ: os._Environ, user: commons.User, filename: str, error_description: str, respond: method)
This function return error 406 web page.
- -
page_500(self, environ: os._Environ, user: commons.User, filename: str, error: Union[str, bytes, Iterable[bytes]], respond: method) -> List[bytes]
This function return error 500 web page.
- -
parse_body(self, environ: os._Environ) -> Tuple[~Content, str, bool]
This function returns arguments from body.
- -
return_inputs(self, arguments: List[Dict[str, ~JsonValue]], is_webscripts_request: bool) -> Tuple[List[str], List[str]]
This function returns inputs (using Server.get_inputs).
- -
send_custom_error(self, environ: os._Environ, user: commons.User, filename: str, error: str, code: str) -> Tuple[Optional[str], Optional[Dict[str, str]], Optional[str]]
This function call custom errors pages.
- -
send_error_page(self, environ: os._Environ, user: commons.User, filename: str, error: str, data: bytes, respond: method) -> List[bytes]
This function send HTTP errors.
- -
send_headers(self, environ: os._Environ, respond: method, error: str = None, headers: Dict[str, str] = None) -> None
This function send error code, message and headers.
- -
set_default_values_for_response(self, error: str, headers: Dict[str, str]) -> Tuple[str, Dict[str, str]]
This function returns default error if not defined and
-default headers updated with custom headers.
- -
-Static methods defined here:
-
check_origin(environ_getter: collections.abc.Callable, environ: os._Environ) -> bool
This function checks Origin of POST methods.
- -
get_attributes(object_: object, attributes: List[str], is_not_package: bool = True) -> Tuple[~FunctionOrNone, bool]
This function get recursive attribute from object.
- -
get_baseurl(environ_getter: collections.abc.Callable, environ: os._Environ) -> str
This function returns URL base.
- -
get_content_length(environ: os._Environ) -> int
This function returns the content length.
- -
get_fullurl(environ: os._Environ) -> str
This function returns the full URL (based on the PEP 3333).

-Link: https://peps.python.org/pep-3333/
- -
get_inputs(arguments: List[Dict[str, ~JsonValue]]) -> Tuple[List[str], List[str]]
This function returns inputs and arguments from arguments.
- -
get_json_content(body: bytes, content_type: str) -> ~JsonValue
This functions returns the loaded JSON content.
- -
return_page(page: Union[bytes, str, Iterable[bytes]]) -> List[bytes]
This function returns response as a list of bytes.
- -
set_default_headers(headers: Dict[str, str], security: bool, configuration: WebScripts.Configuration) -> None
This function sets defaults headers.
- -
try_get_command(body: Dict[str, ~JsonValue]) -> Optional[Tuple[~Content, str, bool]]
This function returns arguments, CSRF token and True if is WebScripts
-request. If is not a WebScripts request because there's no "arguments"
-section in request content, this function returns None. If an error
-is raised in arguments parser, this function returns the JSON
-content, None and False.
- -
use_basic_auth(credentials: str, pages: Pages.Pages, *args) -> Tuple[str, Dict[str, str], str]
This function decodes basic auth and
-authenticates user with it.
- -
-Data descriptors defined here:
-
__dict__
-
dictionary for instance variables (if defined)
-
-
__weakref__
-
list of weak references to the object (if defined)
-
-
-Data and other attributes defined here:
-
CommonsClasses = <class 'WebScripts.Server.CommonsClasses'>
- -

- - - - - -
 
-Functions
       
main() -> int
Main function to build the
-configurations and launch the server.
-

- - - - - -
 
-Data
       __all__ = ['Configuration', 'Server', 'main']
-__author_email__ = 'mauricelambert434@gmail.com'
-__copyright__ = '\nWebScripts Copyright (C) 2021, 2022, 2023 Mau...ome to redistribute it\nunder certain conditions.\n'
-__description__ = '\nThis tool runs CLI scripts and displays output ...,\nthe Server class and the Configuration class).\n'
-__license__ = 'GPL-3.0 License'
-__maintainer__ = 'Maurice Lambert'
-__maintainer_email__ = 'mauricelambert434@gmail.com'
-__url__ = 'https://github.com/mauricelambert/WebScripts'

- - - - - -
 
-Author
       Maurice Lambert
- \ No newline at end of file diff --git a/static/html/__init__.html b/static/html/__init__.html deleted file mode 100644 index 44280363..00000000 --- a/static/html/__init__.html +++ /dev/null @@ -1,237 +0,0 @@ - -Python: module __init__ - - - - - -
 
- 
__init__ (version 1.2.1)
index
__init__.py
-

This package implements a web server to run scripts or 
-executables from the command line and display the result 
-in a web interface.

-

- - - - - -
 
-Classes
       
-
WebScripts.utils.DefaultNamespace(types.SimpleNamespace) -
-
-
WebScripts.WebScripts.Configuration -
-
-
builtins.object -
-
-
WebScripts.WebScripts.Server -
-
-
-

- - - - - - - -
 
-class Configuration(WebScripts.utils.DefaultNamespace)
   Configuration(required: List[str] = [], optional: List[str] = [], default: dict = {}, types: dict = {})

-This class build the configuration from dict(s) with
-configuration files and arguments.
 
 
Method resolution order:
-
Configuration
-
WebScripts.utils.DefaultNamespace
-
types.SimpleNamespace
-
builtins.object
-
-
-Methods defined here:
-
add_conf(self, **kwargs)
Add configurations from other configuration files found.
- -
-Data and other attributes defined here:
-
__defaults__ = {'accept_unauthenticated_user': True, 'accept_unknow_user': True, 'active_auth': False, 'auth_script': None, 'documentations_path': [], 'interface': '127.0.0.1', 'js_path': [], 'modules': [], 'modules_path': [], 'port': 8000, ...}
- -
__optional__ = ('debug', 'security', 'active_auth', 'auth_script', 'accept_unknow_user', 'accept_unauthenticated_user', 'modules', 'modules_path', 'js_path', 'statics_path', 'documentations_path', 'scripts_path', 'json_scripts_config', 'ini_scripts_configlog_level', 'log_filename', 'log_level', 'log_format', 'log_date_format', 'log_encoding', 'auth_failures_to_blacklist', ...)
- -
__required__ = ('interface', 'port')
- -
__types__ = {'accept_unauthenticated_user': <class 'bool'>, 'accept_unknow_user': <class 'bool'>, 'active_auth': <class 'bool'>, 'auth_failures_to_blacklist': <class 'int'>, 'blacklist_time': <class 'int'>, 'debug': <class 'bool'>, 'documentations_path': <class 'list'>, 'ini_scripts_config': <class 'list'>, 'js_path': <class 'list'>, 'json_scripts_config': <class 'list'>, ...}
- -
-Methods inherited from WebScripts.utils.DefaultNamespace:
-
__getitem__(self, key: str)
Compatibility with dict.
- -
__init__(self, required: List[str] = [], optional: List[str] = [], default: dict = {}, types: dict = {})
Initialize self.  See help(type(self)) for accurate signature.
- -
build_types(self) -> None
This function build type from configuration values.
- -
check_required(self) -> None
This function checks required attributes
-if one of required attributes is missing this
-function raise MissingAttributesError.
- -
export_as_json(self, name: str = None) -> None
This function export namespace values (useful for debugging).
- -
get(self, key: str, default=None)
Compatibility with dict.
- -
get_dict(self) -> None
This function return a dict of attributes.
- -
get_missings(self) -> List[str]
This function checks required attributes
-and return a List[str] of missing required attributes.
- -
get_unexpecteds(self, log: bool = True) -> List[str]
This function return a List[str] of
-all attributes not in optional and
-required attributes.

-If log argument is True a Warning log message is
-write for all unexpected attributes.
- -
set_defaults(self) -> None
This function set defaults attribut with defaults values.
- -
update(self, **kwargs)
This function add/update attributes with **kwargs arguments.
- -
-Class methods inherited from WebScripts.utils.DefaultNamespace:
-
default_build(**kwargs) -> ~DefaultNamespace from builtins.type
Default build for DefaultNamespace (set defaults, add values, check
-requirements and unexpected values and build types).
- -
-Data descriptors inherited from WebScripts.utils.DefaultNamespace:
-
__weakref__
-
list of weak references to the object (if defined)
-
-
-Methods inherited from types.SimpleNamespace:
-
__delattr__(self, name, /)
Implement delattr(self, name).
- -
__eq__(self, value, /)
Return self==value.
- -
__ge__(self, value, /)
Return self>=value.
- -
__getattribute__(self, name, /)
Return getattr(self, name).
- -
__gt__(self, value, /)
Return self>value.
- -
__le__(self, value, /)
Return self<=value.
- -
__lt__(self, value, /)
Return self<value.
- -
__ne__(self, value, /)
Return self!=value.
- -
__reduce__(...)
Return state information for pickling
- -
__repr__(self, /)
Return repr(self).
- -
__setattr__(self, name, value, /)
Implement setattr(self, name, value).
- -
-Static methods inherited from types.SimpleNamespace:
-
__new__(*args, **kwargs) from builtins.type
Create and return a new object.  See help(type) for accurate signature.
- -
-Data descriptors inherited from types.SimpleNamespace:
-
__dict__
-
-
-Data and other attributes inherited from types.SimpleNamespace:
-
__hash__ = None
- -

- - - - - - - -
 
-class Server(builtins.object)
   Server(configuration: WebScripts.WebScripts.Configuration)

-This class implement the WebScripts server.
 
 Methods defined here:
-
__init__(self, configuration: WebScripts.WebScripts.Configuration)
Initialize self.  See help(type(self)) for accurate signature.
- -
add_module_or_package(self) -> None
This function add packages and modules to build custom page.
- -
add_paths(self) -> None
This function add js, static and scripts paths.
- -
app(self, environ: os._Environ, respond: function) -> List[bytes]
This function get function page,
-return content page, catch errors and
-return HTTP errors.
- -
check_auth(self, environ: os._Environ) -> Tuple[WebScripts.commons.User, bool]
This function check if user is authenticated and blacklisted.
- -
check_blacklist(self, user: WebScripts.commons.User, ip: str) -> bool
This function checks that the IP and the
-username are not in the blacklist.
- -
get_URLs(self) -> List[str]
This function return a list of string.
- -
get_attributes(self, object_: object, attributes: List[str]) -> Tuple[~FunctionOrNone, str]
This function get recursive attribute from object.
- -
get_function_page(self, path: str) -> ~FunctionOrNone
This function find function from URL path.
- -
get_inputs(self, arguments: List[Dict[str, ~JsonValue]]) -> Tuple[List[str], Dict[str, ~JsonValue]]
This function returns inputs and arguments from arguments.
- -
get_session(self, cookies: List[str], ip: str) -> WebScripts.commons.User
This function return User from cookies.
- -
page_401(self, error_description: str, respond: function)
This function return error 401 web page.
- -
page_403(self, error_description: str, respond: function)
This function return error 403 web page.
- -
page_404(self, url: str, respond: function)
This function return error 404 web page.
- -
page_500(self, error: str, respond: function) -> List[bytes]
This function return error 500 web page.
- -
parse_body(self, environ: os._Environ) -> Tuple[List[Dict[str, ~JsonValue]], str]
This function return arguments from body.
- -
send_custom_error(self, error: str, code: str) -> Tuple[str, Dict[str, str], str]
This function call custom errors pages.
- -
send_error_page(self, error: str, data: bytes, respond: function) -> List[bytes]
This function send HTTP errors.
- -
send_headers(self, respond: function, error: str = None, headers: Dict[str, str] = None) -> None
This function send error code, message and headers.
- -
-Data descriptors defined here:
-
__dict__
-
dictionary for instance variables (if defined)
-
-
__weakref__
-
list of weak references to the object (if defined)
-
-

- - - - - -
 
-Functions
       
main() -> None
Main function to launch server, get configuration and logs.
-

- - - - - -
 
-Data
       __all__ = ['Configuration', 'Server', 'main']
-__author_email__ = 'mauricelambert434@gmail.com'
-__copyright__ = '\nWebScripts Copyright (C) 2021 Maurice Lambert...ome to redistribute it\nunder certain conditions.\n'
-__description__ = 'This package implements a web server to run scri...d line and display the result in a web interface.'
-__license__ = 'GPL-3.0 License'
-__maintainer__ = 'Maurice Lambert'
-__maintainer_email__ = 'mauricelambert434@gmail.com'
-__url__ = 'https://github.com/mauricelambert/WebScripts'

- - - - - -
 
-Author
       Maurice Lambert
- \ No newline at end of file diff --git a/static/html/cgi.html b/static/html/cgi.html deleted file mode 100644 index df4a2e26..00000000 --- a/static/html/cgi.html +++ /dev/null @@ -1,322 +0,0 @@ - -Python: module cgi - - - - - -
 
- 
cgi (version 2.6)
index
c:\program files\python310\lib\cgi.py
Module Reference
-

Support module for CGI (Common Gateway Interface) scripts.

-This module defines a number of utilities for use by CGI scripts
-written in Python.

-

- - - - - -
 
-Modules
       
html
-locale
-
os
-sys
-
tempfile
-urllib
-
warnings
-

- - - - - -
 
-Classes
       
-
builtins.object -
-
-
FieldStorage -
MiniFieldStorage -
-
-
-

- - - - - - - -
 
-class FieldStorage(builtins.object)
   FieldStorage(fp=None, headers=None, outerboundary=b'', {}, keep_blank_values=0, strict_parsing=0, limit=None, encoding='utf-8', errors='replace', max_num_fields=None, separator='&amp;')

-Store a sequence of fields, reading multipart/form-data.

-This class provides naming, typing, files stored on disk, and
-more.  At the top level, it is accessible like a dictionary, whose
-keys are the field names.  (Note: None can occur as a field name.)
-The items are either a Python list (if there's multiple values) or
-another FieldStorage or MiniFieldStorage object.  If it's a single
-object, it has the following attributes:

-name: the field name, if specified; otherwise None

-filename: the filename, if specified; otherwise None; this is the
-    client side filename, *not* the file name on which it is
-    stored (that's a temporary file you don't deal with)

-value: the value as a *string*; for file uploads, this
-    transparently reads the file every time you request the value
-    and returns *bytes*

-file: the file(-like) object from which you can read the data *as
-    bytes* ; None if the data is stored a simple string

-type: the content-type, or None if not specified

-type_options: dictionary of options specified on the content-type
-    line

-disposition: content-disposition, or None if not specified

-disposition_options: dictionary of corresponding options

-headers: a dictionary(-like) object (sometimes email.message.Message or a
-    subclass thereof) containing *all* headers

-The class is subclassable, mostly for the purpose of overriding
-the make_file() method, which is called internally to come up with
-a file open for reading and writing.  This makes it possible to
-override the default choice of storing all files in a temporary
-directory and unlinking them as soon as they have been opened.
 
 Methods defined here:
-
__bool__(self)
- -
__contains__(self, key)
Dictionary style __contains__ method.
- -
__del__(self)
- -
__enter__(self)
- -
__exit__(self, *args)
- -
__getattr__(self, name)
- -
__getitem__(self, key)
Dictionary style indexing.
- -
__init__(self, fp=None, headers=None, outerboundary=b'', {}, keep_blank_values=0, strict_parsing=0, limit=None, encoding='utf-8', errors='replace', max_num_fields=None, separator='&')
Constructor.  Read multipart/* until last part.

-Arguments, all optional:

-fp              : file pointer; default: sys.stdin.buffer
-    (not used when the request method is GET)
-    Can be :
-    1. a TextIOWrapper object
-    2. an object whose read() and readline() methods return bytes

-headers         : header dictionary-like object; default:
-    taken from environ as per CGI spec

-outerboundary   : terminating multipart boundary
-    (for internal use only)

-environ         : environment dictionary; default: os.environ

-keep_blank_values: flag indicating whether blank values in
-    percent-encoded forms should be treated as blank strings.
-    A true value indicates that blanks should be retained as
-    blank strings.  The default false value indicates that
-    blank values are to be ignored and treated as if they were
-    not included.

-strict_parsing: flag indicating what to do with parsing errors.
-    If false (the default), errors are silently ignored.
-    If true, errors raise a ValueError exception.

-limit : used internally to read parts of multipart/form-data forms,
-    to exit from the reading loop when reached. It is the difference
-    between the form content-length and the number of bytes already
-    read

-encoding, errors : the encoding and error handler used to decode the
-    binary stream to strings. Must be the same as the charset defined
-    for the page sending the form (content-type : meta http-equiv or
-    header)

-max_num_fields: int. If set, then __init__ throws a ValueError
-    if there are more than n fields read by parse_qsl().
- -
__iter__(self)
- -
__len__(self)
Dictionary style len(x) support.
- -
__repr__(self)
Return a printable representation.
- -
getfirst(self, key, default=None)
Return the first value received.
- -
getlist(self, key)
Return list of received values.
- -
getvalue(self, key, default=None)
Dictionary style get() method, including 'value' lookup.
- -
keys(self)
Dictionary style keys() method.
- -
make_file(self)
Overridable: return a readable & writable file.

-The file will be used as follows:
-- data is written to it
-- seek(0)
-- data is read from it

-The file is opened in binary mode for files, in text mode
-for other fields

-This version opens a temporary file for reading and writing,
-and immediately deletes (unlinks) it.  The trick (on Unix!) is
-that the file can still be used, but it can't be opened by
-another process, and it will automatically be deleted when it
-is closed or when the current process terminates.

-If you want a more permanent file, you derive a class which
-overrides this method.  If you want a visible temporary file
-that is nevertheless automatically deleted when the script
-terminates, try defining a __del__ method in a derived class
-which unlinks the temporary files you have created.
- -
read_binary(self)
Internal: read binary data.
- -
read_lines(self)
Internal: read lines until EOF or outerboundary.
- -
read_lines_to_eof(self)
Internal: read lines until EOF.
- -
read_lines_to_outerboundary(self)
Internal: read lines until outerboundary.
-Data is read as bytes: boundaries and line ends must be converted
-to bytes for comparisons.
- -
read_multi(self, environ, keep_blank_values, strict_parsing)
Internal: read a part that is itself multipart.
- -
read_single(self)
Internal: read an atomic part.
- -
read_urlencoded(self)
Internal: read data in query string format.
- -
skip_lines(self)
Internal: skip lines until outer boundary if defined.
- -
-Data descriptors defined here:
-
__dict__
-
dictionary for instance variables (if defined)
-
-
__weakref__
-
list of weak references to the object (if defined)
-
-
-Data and other attributes defined here:
-
FieldStorageClass = None
- -
bufsize = 8192
- -

- - - - - - - -
 
-class MiniFieldStorage(builtins.object)
   MiniFieldStorage(name, value)

-Like FieldStorage, for use when no file uploads are possible.
 
 Methods defined here:
-
__init__(self, name, value)
Constructor from field name and value.
- -
__repr__(self)
Return printable representation.
- -
-Data descriptors defined here:
-
__dict__
-
dictionary for instance variables (if defined)
-
-
__weakref__
-
list of weak references to the object (if defined)
-
-
-Data and other attributes defined here:
-
disposition = None
- -
disposition_options = {}
- -
file = None
- -
filename = None
- -
headers = {}
- -
list = None
- -
type = None
- -
type_options = {}
- -

- - - - - -
 
-Functions
       
parse(fp=None, {}, keep_blank_values=0, strict_parsing=0, separator='&')
Parse a query in the environment or from a file (default stdin)

-Arguments, all optional:

-fp              : file pointer; default: sys.stdin.buffer

-environ         : environment dictionary; default: os.environ

-keep_blank_values: flag indicating whether blank values in
-    percent-encoded forms should be treated as blank strings.
-    A true value indicates that blanks should be retained as
-    blank strings.  The default false value indicates that
-    blank values are to be ignored and treated as if they were
-    not included.

-strict_parsing: flag indicating what to do with parsing errors.
-    If false (the default), errors are silently ignored.
-    If true, errors raise a ValueError exception.

-separator: str. The symbol to use for separating the query arguments.
-    Defaults to &.
-
parse_header(line)
Parse a Content-type like header.

-Return the main content-type and a dictionary of options.
-
parse_multipart(fp, pdict, encoding='utf-8', errors='replace', separator='&')
Parse multipart input.

-Arguments:
-fp   : input file
-pdict: dictionary containing other parameters of content-type header
-encoding, errors: request encoding and error handler, passed to
-    FieldStorage

-Returns a dictionary just like parse_qs(): keys are the field names, each
-value is a list of values for that field. For non-file fields, the value
-is a list of strings.
-
print_arguments()
-
print_directory()
Dump the current directory as HTML.
-
print_environ({})
Dump the shell environment as HTML.
-
print_environ_usage()
Dump a list of environment variables used by CGI as HTML.
-
print_exception(type=None, value=None, tb=None, limit=None)
-
print_form(form)
Dump the contents of a form as HTML.
-
test({})
Robust test CGI script, usable as main program.

-Write minimal HTTP headers and dump all information provided to
-the script in HTML form.
-

- - - - - -
 
-Data
       __all__ = ['MiniFieldStorage', 'FieldStorage', 'parse', 'parse_multipart', 'parse_header', 'test', 'print_exception', 'print_environ', 'print_form', 'print_directory', 'print_arguments', 'print_environ_usage']
- \ No newline at end of file diff --git a/static/html/commons.html b/static/html/commons.html deleted file mode 100644 index dffcd430..00000000 --- a/static/html/commons.html +++ /dev/null @@ -1,584 +0,0 @@ - -Python: module commons - - - - - -
 
- 
commons (version 0.1.7)
index
commons.py
-

This tool runs CLI scripts and displays output in a Web Interface.

-This file implements commons functions and class for WebScripts package.

-

- - - - - -
 
-Classes
       
-
builtins.object -
-
-
Blacklist -
Session -
TokenCSRF -
-
-
collections.abc.Callable(builtins.object) -
-
-
CallableFile -
-
-
utils.DefaultNamespace(types.SimpleNamespace) -
-
-
Argument -
ScriptConfig -
User -
-
-
-

- - - - - - - -
 
-class Argument(utils.DefaultNamespace)
   Argument(required: List[str] = [], optional: List[str] = [], default: dict = {}, types: dict = {})

-This class build argument for script.
 
 
Method resolution order:
-
Argument
-
utils.DefaultNamespace
-
types.SimpleNamespace
-
builtins.object
-
-
-Static methods defined here:
-
get_command(name: str, argument: Dict[str, ~JsonValue]) -> List[Dict[str, ~JsonValue]]
This function return list for command line execution.
- -
-Data and other attributes defined here:
-
__defaults__ = {'default_value': None, 'description': None, 'example': None, 'html_type': 'text', 'input': None, 'is_advanced': False, 'javascript_attributs': {}}
- -
__optional__ = ['list', 'input', 'example', 'html_type', 'is_advanced', 'description', 'default_value', 'predefined_values', 'javascript_attributs']
- -
__required__ = ['name']
- -
__types__ = {'input': <class 'bool'>, 'is_advanced': <class 'bool'>, 'list': <class 'bool'>, 'predefined_values': <class 'list'>}
- -
-Methods inherited from utils.DefaultNamespace:
-
__getitem__(self, key: str)
Compatibility with dict.
- -
__init__(self, required: List[str] = [], optional: List[str] = [], default: dict = {}, types: dict = {})
Initialize self.  See help(type(self)) for accurate signature.
- -
build_type(self, attribut: str, value: Any, type_: type = None) -> None
This function builds type from configuration value.
- -
build_types(self) -> None
This function builds type from configuration values.
- -
check_required(self) -> None
This function checks required attributes
-if one of required attributes is missing this
-function raise MissingAttributesError.
- -
export_as_json(self, name: str = None) -> None
This function export namespace values (useful for debugging).
- -
get(self, key: str, default=None)
Compatibility with dict.
- -
get_dict(self) -> None
This function return a dict of attributes.
- -
get_missings(self) -> List[str]
This function checks required attributes
-and return a List[str] of missing required attributes.
- -
get_unexpecteds(self, log: bool = True) -> List[str]
This function return a List[str] of
-all attributes not in optional and
-required attributes.

-If log argument is True a Warning log message is
-write for all unexpected attributes.
- -
set_defaults(self) -> None
This function set defaults attribut with defaults values.
- -
update(self, **kwargs)
This function add/update attributes with **kwargs arguments.
- -
-Class methods inherited from utils.DefaultNamespace:
-
default_build(**kwargs) -> ~DefaultNamespace from builtins.type
Default build for DefaultNamespace (set defaults, add values,
-check requirements and unexpected values and build types).
- -
-Data descriptors inherited from utils.DefaultNamespace:
-
__weakref__
-
list of weak references to the object (if defined)
-
-
-Methods inherited from types.SimpleNamespace:
-
__delattr__(self, name, /)
Implement delattr(self, name).
- -
__eq__(self, value, /)
Return self==value.
- -
__ge__(self, value, /)
Return self>=value.
- -
__getattribute__(self, name, /)
Return getattr(self, name).
- -
__gt__(self, value, /)
Return self>value.
- -
__le__(self, value, /)
Return self<=value.
- -
__lt__(self, value, /)
Return self<value.
- -
__ne__(self, value, /)
Return self!=value.
- -
__reduce__(...)
Return state information for pickling
- -
__repr__(self, /)
Return repr(self).
- -
__setattr__(self, name, value, /)
Implement setattr(self, name, value).
- -
-Static methods inherited from types.SimpleNamespace:
-
__new__(*args, **kwargs) from builtins.type
Create and return a new object.  See help(type) for accurate signature.
- -
-Data descriptors inherited from types.SimpleNamespace:
-
__dict__
-
-
-Data and other attributes inherited from types.SimpleNamespace:
-
__hash__ = None
- -

- - - - - - - -
 
-class Blacklist(builtins.object)
   Blacklist(configuration: ~ServerConfiguration, last_blacklist: ~Blacklist = None)

-This class implement blacklist.
 
 Methods defined here:
-
__init__(self, configuration: ~ServerConfiguration, last_blacklist: ~Blacklist = None)
Initialize self.  See help(type(self)) for accurate signature.
- -
__str__(self) -> str
This function returns a string to represent the Blacklist object.
- -
is_blacklist(self, configuration: ~ServerConfiguration) -> bool
This function return True if this object is blacklisted.
- -
-Data descriptors defined here:
-
__dict__
-
dictionary for instance variables (if defined)
-
-
__weakref__
-
list of weak references to the object (if defined)
-
-

- - - - - - - -
 
-class CallableFile(collections.abc.Callable)
   CallableFile(type_: str, path_: str, filename: str, config: dict = None, security: bool = True)

-This class build callable object to return
-Web files content or script output.
 
 
Method resolution order:
-
CallableFile
-
collections.abc.Callable
-
builtins.object
-
-
-Methods defined here:
-
__call__(self, user: commons.User, subdirectory: int = 2) -> Tuple[str, Dict[str, str], List[bytes]]
Call self as a function.
- -
__init__(self, type_: str, path_: str, filename: str, config: dict = None, security: bool = True)
Initialize self.  See help(type(self)) for accurate signature.
- -
is_html(self) -> bool
This function compare extension with html extensions.
- -
is_jpeg(self) -> bool
This function compare extension with jpeg extensions.
- -
is_tiff(self) -> bool
This function compare extension with tif extensions.
- -
is_xml(self) -> bool
This function compare extension with xml extensions.
- -
-Data descriptors defined here:
-
__dict__
-
dictionary for instance variables (if defined)
-
-
__weakref__
-
list of weak references to the object (if defined)
-
-
-Data and other attributes defined here:
-
__abstractmethods__ = frozenset()
- -
__annotations__ = {'template_footer': <class 'str'>, 'template_footer_path': <class 'str'>, 'template_header': <class 'str'>, 'template_header_path': <class 'str'>, 'template_index': <class 'str'>, 'template_index_path': <class 'str'>, 'template_script': <class 'str'>, 'template_script_path': <class 'str'>}
- -
template_footer = '\t\t<footer id="webscripts_footer" class="footer b...bScripts/share.html">online</a>.</li>\n\t\t</footer>'
- -
template_footer_path = r'/home/WebScripts\static\templates\footer.html'
- -
template_header = '\t\t<header id="webscripts_header" class="header b...s_header_canvas_container"></a></div>\n\t\t</header>'
- -
template_header_path = r'/home/WebScripts\static\templates\header.html'
- -
template_index = '<!--\n\n HTML page for index.\n Copyright (C)...content">\n\t\t</div>\n\n\t\t%(footer)s\n\t</body>\n</html>'
- -
template_index_path = r'/home/WebScripts\static\templates\index.html'
- -
template_script = '<!--\n\n HTML page to launch scripts.\n Copyr...ath = "%(subpath)s";\n\t\t</script>\n\t</body>\n</html>'
- -
template_script_path = r'/home/WebScripts\static\templates\script.html'
- -
-Class methods inherited from collections.abc.Callable:
-
__class_getitem__ = _CallableGenericAlias(args) from abc.ABCMeta
Represent `Callable[argtypes, resulttype]`.

-This sets ``__args__`` to a tuple containing the flattened ``argtypes``
-followed by ``resulttype``.

-Example: ``Callable[[int, str], float]`` sets ``__args__`` to
-``(int, str, float)``.
- -
__subclasshook__(C) from abc.ABCMeta
Abstract classes can override this to customize issubclass().

-This is invoked early on by abc.ABCMeta.__subclasscheck__().
-It should return True, False or NotImplemented.  If it returns
-NotImplemented, the normal algorithm is used.  Otherwise, it
-overrides the normal algorithm (and the outcome is cached).
- -

- - - - - - - -
 
-class ScriptConfig(utils.DefaultNamespace)
   ScriptConfig(required: List[str] = [], optional: List[str] = [], default: dict = {}, types: dict = {})

-This class makes script configurations.
 
 
Method resolution order:
-
ScriptConfig
-
utils.DefaultNamespace
-
types.SimpleNamespace
-
builtins.object
-
-
-Methods defined here:
-
build_args(self, configuration: ~Configuration)
This function build Arguments from self.args: List[Dict[str, str]]
- -
get_JSON_API(self) -> Dict
This function return a dict for JSON API
-(visible configuration for user).
- -
-Class methods defined here:
-
build_scripts_from_configuration(server_configuration: ~ServerConfiguration) -> Dict[str, ~ScriptConfig] from builtins.type
This function build scripts from server
-configuration and configurations files.
- -
get_scripts_from_configuration(configuration: ~Configuration, server_configuration: ~ServerConfiguration) -> Dict[str, ~ScriptConfig] from builtins.type
This function build scripts from ServerConfiguration.
- -
-Static methods defined here:
-
get_Windows_default_script_launcher(script_config: Dict[str, ~JsonValue]) -> str
This function gets the Windows default launcher to execute a file.
- -
get_arguments_from_config(arguments_section: str, configuration: Dict[str, Dict[str, ~JsonValue]]) -> List[Dict[str, ~JsonValue]]
This function get arguments list of script.
- -
get_docfile_from_configuration(configuration: ~ServerConfiguration, filename: str) -> str
This method returns the script documentation
-path if it exists.
- -
get_documentation_from_configuration(script_config: Dict[str, ~JsonValue], name: str, paths: List[str]) -> str
This function get documentation from script configuration
-or search it in documentation path.
- -
get_script_config_from_specific_file_config(script_config: Dict[str, ~JsonValue], configuration: Dict[str, ~JsonValue], server_configuration: Dict[str, ~JsonValue]) -> Tuple[dict, dict]
This function return all configuration and
-script configuration from configuration.
- -
get_script_path(server_configuration: ~ServerConfiguration, script_config: Dict[str, ~JsonValue]) -> str
This function return a script path from configuration.
- -
-Data and other attributes defined here:
-
__defaults__ = {'access_groups': None, 'access_users': None, 'args': [], 'category': '', 'command_generate_documentation': None, 'content_type': 'text/plain', 'description': None, 'documentation_content_type': 'text/html', 'documentation_file': None, 'launcher': None, ...}
- -
__optional__ = ['command_generate_documentation', 'documentation_content_type', 'stderr_content_type', 'documentation_file', 'minimum_access', 'access_groups', 'content_type', 'access_users', 'no_password', 'description', 'category', 'launcher', 'timeout', 'path', 'args']
- -
__required__ = ['name', 'dirname']
- -
__types__ = {'access_groups': typing.List[int], 'access_users': typing.List[int], 'minimum_access': <class 'int'>, 'no_password': <class 'bool'>, 'timeout': <class 'int'>}
- -
-Methods inherited from utils.DefaultNamespace:
-
__getitem__(self, key: str)
Compatibility with dict.
- -
__init__(self, required: List[str] = [], optional: List[str] = [], default: dict = {}, types: dict = {})
Initialize self.  See help(type(self)) for accurate signature.
- -
build_type(self, attribut: str, value: Any, type_: type = None) -> None
This function builds type from configuration value.
- -
build_types(self) -> None
This function builds type from configuration values.
- -
check_required(self) -> None
This function checks required attributes
-if one of required attributes is missing this
-function raise MissingAttributesError.
- -
export_as_json(self, name: str = None) -> None
This function export namespace values (useful for debugging).
- -
get(self, key: str, default=None)
Compatibility with dict.
- -
get_dict(self) -> None
This function return a dict of attributes.
- -
get_missings(self) -> List[str]
This function checks required attributes
-and return a List[str] of missing required attributes.
- -
get_unexpecteds(self, log: bool = True) -> List[str]
This function return a List[str] of
-all attributes not in optional and
-required attributes.

-If log argument is True a Warning log message is
-write for all unexpected attributes.
- -
set_defaults(self) -> None
This function set defaults attribut with defaults values.
- -
update(self, **kwargs)
This function add/update attributes with **kwargs arguments.
- -
-Class methods inherited from utils.DefaultNamespace:
-
default_build(**kwargs) -> ~DefaultNamespace from builtins.type
Default build for DefaultNamespace (set defaults, add values,
-check requirements and unexpected values and build types).
- -
-Data descriptors inherited from utils.DefaultNamespace:
-
__weakref__
-
list of weak references to the object (if defined)
-
-
-Methods inherited from types.SimpleNamespace:
-
__delattr__(self, name, /)
Implement delattr(self, name).
- -
__eq__(self, value, /)
Return self==value.
- -
__ge__(self, value, /)
Return self>=value.
- -
__getattribute__(self, name, /)
Return getattr(self, name).
- -
__gt__(self, value, /)
Return self>value.
- -
__le__(self, value, /)
Return self<=value.
- -
__lt__(self, value, /)
Return self<value.
- -
__ne__(self, value, /)
Return self!=value.
- -
__reduce__(...)
Return state information for pickling
- -
__repr__(self, /)
Return repr(self).
- -
__setattr__(self, name, value, /)
Implement setattr(self, name, value).
- -
-Static methods inherited from types.SimpleNamespace:
-
__new__(*args, **kwargs) from builtins.type
Create and return a new object.  See help(type) for accurate signature.
- -
-Data descriptors inherited from types.SimpleNamespace:
-
__dict__
-
-
-Data and other attributes inherited from types.SimpleNamespace:
-
__hash__ = None
- -

- - - - - - - -
 
-class Session(builtins.object)
   Session(user: commons.User, ip: str)

-Object to implement session.
 
 Methods defined here:
-
__init__(self, user: commons.User, ip: str)
Initialize self.  See help(type(self)) for accurate signature.
- -
__str__(self) -> str
This function returns a string to represent the Session object.
- -
-Class methods defined here:
-
build_session(user: commons.User, ip: str, Pages: ~Pages) -> str from builtins.type
This function build and add session and return the cookie.
- -
-Static methods defined here:
-
check_session(cookie: str, pages: ~Pages, ip: str, default_user: commons.User, session_max_time: float = 3600) -> commons.User
This function check session validity and return user.
- -
-Data descriptors defined here:
-
__dict__
-
dictionary for instance variables (if defined)
-
-
__weakref__
-
list of weak references to the object (if defined)
-
-

- - - - - - - -
 
-class TokenCSRF(builtins.object)
   This class brings together the functions related to the CSRF token
 
 Static methods defined here:
-
build_token(user: commons.User) -> str
This function build a CSRF token for a user.
- -
check_csrf(user: commons.User, token: str, csrf_max_time: float = 300, referer: str = None, baseurl: str = None) -> bool
This function check the validity of a csrf token.
- -
clean(user: commons.User, max_time: float) -> None
This function clean all old CSRF tokens for a user.
- -
-Data descriptors defined here:
-
__dict__
-
dictionary for instance variables (if defined)
-
-
__weakref__
-
list of weak references to the object (if defined)
-
-

- - - - - - - -
 
-class User(utils.DefaultNamespace)
   User(required: List[str] = [], optional: List[str] = [], default: dict = {}, types: dict = {})

-This class implements User object.
 
 
Method resolution order:
-
User
-
utils.DefaultNamespace
-
types.SimpleNamespace
-
builtins.object
-
-
-Data and other attributes defined here:
-
__defaults__ = {'categories': ['*'], 'check_csrf': False, 'csrf': {}, 'groups': [], 'scripts': ['*']}
- -
__required__ = ['id', 'name', 'groups', 'csrf', 'ip', 'categories', 'scripts', 'check_csrf']
- -
__types__ = {'categories': <class 'list'>, 'check_csrf': <class 'bool'>, 'groups': typing.List[int], 'id': <class 'int'>, 'scripts': <class 'list'>}
- -
-Methods inherited from utils.DefaultNamespace:
-
__getitem__(self, key: str)
Compatibility with dict.
- -
__init__(self, required: List[str] = [], optional: List[str] = [], default: dict = {}, types: dict = {})
Initialize self.  See help(type(self)) for accurate signature.
- -
build_type(self, attribut: str, value: Any, type_: type = None) -> None
This function builds type from configuration value.
- -
build_types(self) -> None
This function builds type from configuration values.
- -
check_required(self) -> None
This function checks required attributes
-if one of required attributes is missing this
-function raise MissingAttributesError.
- -
export_as_json(self, name: str = None) -> None
This function export namespace values (useful for debugging).
- -
get(self, key: str, default=None)
Compatibility with dict.
- -
get_dict(self) -> None
This function return a dict of attributes.
- -
get_missings(self) -> List[str]
This function checks required attributes
-and return a List[str] of missing required attributes.
- -
get_unexpecteds(self, log: bool = True) -> List[str]
This function return a List[str] of
-all attributes not in optional and
-required attributes.

-If log argument is True a Warning log message is
-write for all unexpected attributes.
- -
set_defaults(self) -> None
This function set defaults attribut with defaults values.
- -
update(self, **kwargs)
This function add/update attributes with **kwargs arguments.
- -
-Class methods inherited from utils.DefaultNamespace:
-
default_build(**kwargs) -> ~DefaultNamespace from builtins.type
Default build for DefaultNamespace (set defaults, add values,
-check requirements and unexpected values and build types).
- -
-Data descriptors inherited from utils.DefaultNamespace:
-
__weakref__
-
list of weak references to the object (if defined)
-
-
-Methods inherited from types.SimpleNamespace:
-
__delattr__(self, name, /)
Implement delattr(self, name).
- -
__eq__(self, value, /)
Return self==value.
- -
__ge__(self, value, /)
Return self>=value.
- -
__getattribute__(self, name, /)
Return getattr(self, name).
- -
__gt__(self, value, /)
Return self>value.
- -
__le__(self, value, /)
Return self<=value.
- -
__lt__(self, value, /)
Return self<value.
- -
__ne__(self, value, /)
Return self!=value.
- -
__reduce__(...)
Return state information for pickling
- -
__repr__(self, /)
Return repr(self).
- -
__setattr__(self, name, value, /)
Implement setattr(self, name, value).
- -
-Static methods inherited from types.SimpleNamespace:
-
__new__(*args, **kwargs) from builtins.type
Create and return a new object.  See help(type) for accurate signature.
- -
-Data descriptors inherited from types.SimpleNamespace:
-
__dict__
-
-
-Data and other attributes inherited from types.SimpleNamespace:
-
__hash__ = None
- -

- - - - - -
 
-Data
       JsonValue = ~JsonValue
-ServerConfiguration = ~ServerConfiguration
-__all__ = ['User', 'Session', 'Argument', 'JsonValue', 'TokenCSRF', 'Blacklist', 'ScriptConfig', 'CallableFile', 'ServerConfiguration']
-__author_email__ = 'mauricelambert434@gmail.com'
-__copyright__ = '\nWebScripts Copyright (C) 2021, 2022, 2023 Mau...ome to redistribute it\nunder certain conditions.\n'
-__description__ = '\nThis tool runs CLI scripts and displays output ...mons functions and class for WebScripts package.\n'
-__license__ = 'GPL-3.0 License'
-__maintainer__ = 'Maurice Lambert'
-__maintainer_email__ = 'mauricelambert434@gmail.com'
-__url__ = 'https://github.com/mauricelambert/WebScripts'

- - - - - -
 
-Author
       Maurice Lambert
- \ No newline at end of file diff --git a/static/html/csp.html b/static/html/csp.html deleted file mode 100644 index b3b191bc..00000000 --- a/static/html/csp.html +++ /dev/null @@ -1,60 +0,0 @@ - -Python: module csp - - - - - -
 
- 
csp (version 0.0.1)
index
csp.py
-

This tool run scripts and display the result in a Web Interface.

-This file implement a Content-Security-Policy debug page.

-

- - - - - -
 
-Modules
       
json
-

- - - - - -
 
-Functions
       
debug(environ: os._Environ, user: ~User, configuration: ~ServerConfiguration, code: str, arguments: Dict[str, Dict[str, str]], inputs: List[str], csrf_token: str = None) -> Tuple[str, Dict[str, str], str]
This function implement a debug page.
-

- - - - - -
 
-Data
       Dict = typing.Dict
-List = typing.List
-ServerConfiguration = ~ServerConfiguration
-Tuple = typing.Tuple
-User = ~User
-__author_email__ = 'mauricelambert434@gmail.com'
-__copyright__ = '\nWebScripts Copyright (C) 2021, 2022 Maurice L...ome to redistribute it\nunder certain conditions.\n'
-__description__ = '\nThis tool run scripts and display the result in...e implement a Content-Security-Policy debug page.'
-__license__ = 'GPL-3.0 License'
-__maintainer__ = 'Maurice Lambert'
-__maintainer_email__ = 'mauricelambert434@gmail.com'
-__url__ = 'https://github.com/mauricelambert/WebScripts'
-copyright = '\nWebScripts Copyright (C) 2021, 2022 Maurice L...ome to redistribute it\nunder certain conditions.\n'
-csp_report = {'report': 'No CSP report yet.'}
-license = 'GPL-3.0 License'

- - - - - -
 
-Author
       Maurice Lambert
- \ No newline at end of file diff --git a/static/html/error_pages.html b/static/html/error_pages.html deleted file mode 100644 index 539038c0..00000000 --- a/static/html/error_pages.html +++ /dev/null @@ -1,119 +0,0 @@ - -Python: module error_pages - - - - - -
 
- 
error_pages (version 0.0.4)
index
error_pages.py
-

This tool run scripts and display the result in a Web Interface.

-This file implement error, report and request pages by default.

-

- - - - - -
 
-Modules
       
csv
-
json
-
ntpath
-
secrets
-

- - - - - -
 
-Classes
       
-
builtins.object -
-
-
Report -
Request -
-
-
-

- - - - - - - -
 
-class Report(builtins.object)
   This class implements pages for the report feature by default.
 
 Methods defined here:
-
new(environ: os._Environ, user: ~User, configuration: ~ServerConfiguration, code: str, arguments: List[str], inputs: List[str], csrf_token: str = None) -> Tuple[str, Dict[str, str], str]
This function returns the report page by default.
- -
-Data descriptors defined here:
-
__dict__
-
dictionary for instance variables (if defined)
-
-
__weakref__
-
list of weak references to the object (if defined)
-
-

- - - - - - - -
 
-class Request(builtins.object)
   This class implements pages for the report feature by default.
 
 Methods defined here:
-
save(username: str, code: str, url: str, user_agent: str, subject: str, name: str, reason: str) -> None
This function save the report/request to a CSV file.
- -
send(environ: os._Environ, user: ~User, configuration: ~ServerConfiguration, code: str, arguments: List[str], inputs: List[str], csrf_token: str = None) -> Tuple[str, Dict[str, str], str]
This function save and send request or report.
- -
send_mail(configuration: ~ServerConfiguration, notification: str) -> None
This function send a notification mail.
- -
-Data descriptors defined here:
-
__dict__
-
dictionary for instance variables (if defined)
-
-
__weakref__
-
list of weak references to the object (if defined)
-
-

- - - - - -
 
-Functions
       
page_401(error: str) -> Tuple[str, Dict[str, str], List[bytes]]
This function uses send_error_page to return the page 401 by default.
-
page_403(error: str) -> Tuple[str, Dict[str, str], List[bytes]]
This function uses send_error_page to return the page 403 by default.
-
page_404(error: str) -> Tuple[str, Dict[str, str], List[bytes]]
This function uses send_error_page to return the page 404 by default.
-
page_500(error: str) -> Tuple[str, Dict[str, str], List[bytes]]
This function uses send_error_page to return the page 500 by default.
-

- - - - - -
 
-Data
       __all__ = ['page_500', 'page_401', 'page_403', 'page_404', 'Request', 'Report']
-__author_email__ = 'mauricelambert434@gmail.com'
-__copyright__ = '\nWebScripts Copyright (C) 2021, 2022 Maurice L...ome to redistribute it\nunder certain conditions.\n'
-__description__ = '\nThis tool run scripts and display the result in...ement errors and report/request pages by default.'
-__license__ = 'GPL-3.0 License'
-__maintainer__ = 'Maurice Lambert'
-__maintainer_email__ = 'mauricelambert434@gmail.com'
-__url__ = 'https://github.com/mauricelambert/WebScripts'

- - - - - -
 
-Author
       Maurice Lambert
- \ No newline at end of file diff --git a/static/html/get_request.html b/static/html/get_request.html deleted file mode 100644 index 053e33a4..00000000 --- a/static/html/get_request.html +++ /dev/null @@ -1,47 +0,0 @@ - -Python: module get_request - - - - - -
 
- 
get_request (version 0.0.1)
index
scripts\request\get_request.py
-

This package implements a web server to run scripts or 
-executables from the command line and display the result 
-in a web interface.

-This file prints a user request.

-

- - - - - -
 
-Modules
       
sys
-

- - - - - -
 
-Data
       __all__ = []
-__author_email__ = 'mauricelambert434@gmail.com'
-__copyright__ = '\nWebScripts Copyright (C) 2021 Maurice Lambert...ome to redistribute it\nunder certain conditions.\n'
-__description__ = 'This package implements a web server to run scri...a web interface.\n\nThis file prints a user request'
-__license__ = 'GPL-3.0 License'
-__maintainer__ = 'Maurice Lambert'
-__maintainer_email__ = 'mauricelambert434@gmail.com'
-__url__ = 'https://github.com/mauricelambert/WebScripts'

- - - - - -
 
-Author
       Maurice Lambert
- \ No newline at end of file diff --git a/static/html/hardening.html b/static/html/hardening.html deleted file mode 100644 index acd57a1f..00000000 --- a/static/html/hardening.html +++ /dev/null @@ -1,299 +0,0 @@ - -Python: module hardening - - - - - -
 
- 
hardening (version 1.1.8)
index
hardening.py
-

This tool runs CLI scripts and displays output in a Web Interface.

-This file implement the hardening audit of the WebScripts installation and
-configuration.

-

- - - - - -
 
-Modules
       
ctypes
-
os
-
pip
-

- - - - - -
 
-Classes
       
-
builtins.object -
-
-
Audit -
Report -
Rule -
-
-
enum.Enum(builtins.object) -
-
-
SEVERITY -
-
-
-

- - - - - - - -
 
-class Audit(builtins.object)
   This function implement hardening checks.
 
 Methods defined here:
-
audit_active_auth(server: ~Server) -> hardening.Rule
This function checks authentication is enabled.
- -
audit_admin_account(server: ~Server) -> collections.abc.Iterator[hardening.Rule]
This function checks the admin password.
- -
audit_blacklist(server: ~Server) -> hardening.Rule
This function checks the blacklist configuration.
- -
audit_config_files(server: ~Server) -> hardening.Rule
This function checks the configurations files.
- -
audit_debug(server: ~Server) -> hardening.Rule
This function checks the debug configuration.
- -
audit_export_configuration(server: ~Server) -> hardening.Rule
This function checks the export configuration file.
- -
audit_force_auth(server: ~Server) -> hardening.Rule
This function checks authentication is forced.
- -
audit_in_venv(server: ~Server) -> hardening.Rule
This function checks the virtualenv.
- -
audit_interface(server: ~Server) -> hardening.Rule
This function checks the network interface.
- -
audit_limit_exclude_auth(server: ~Server) -> hardening.Rule
This function checks exclusions for authentication.
- -
audit_log_level(server: ~Server) -> hardening.Rule
This function checks the log level.
- -
audit_security(server: ~Server) -> hardening.Rule
This function checks the security configuration.
- -
audit_smtp_password(server: ~Server) -> hardening.Rule
This function checks the SMTP password protection.
- -
audit_system_user(server: ~Server) -> hardening.Rule
This function checks the user.
- -
audit_venv_modules(server: ~Server) -> hardening.Rule
This function checks the virtualenv modules.
- -
audit_webproxy_number(server: ~Server) -> hardening.Rule
This function checks exclusions for authentication.
- -
audits_file_owner(server: ~Server) -> collections.abc.Iterator[hardening.Rule]
This function checks the files owner.
- -
audits_file_rights(server: ~Server) -> collections.abc.Iterator[hardening.Rule]
This function checks the files rights.
- -
audits_launcher(server: ~Server) -> collections.abc.Iterator[hardening.Rule]
This function checks the configuration of the script launcher.
- -
audits_module_path(server: ~Server) -> hardening.Rule
This function checks the modules paths.
- -
audits_script_configuration_file_path(server: ~Server) -> collections.abc.Iterator[hardening.Rule]
This function checks the scripts configurations files path
-for absolute path configurations.
- -
audits_scripts_content_type(server: ~Server) -> collections.abc.Iterator[hardening.Rule]
This function checks the configuration of the script content type.
- -
audits_scripts_logs(server: ~Server) -> collections.abc.Iterator[hardening.Rule]
This function checks the configuration of the script log.
- -
audits_scripts_path(server: ~Server) -> collections.abc.Iterator[hardening.Rule]
This function checks the configuration of the script path.
- -
audits_scripts_stderr_content_type(server: ~Server) -> collections.abc.Iterator[hardening.Rule]
This function checks the configuration of the script stderr content
-type.
- -
audits_timeout(server: ~Server) -> collections.abc.Iterator[hardening.Rule]
This function checks scripts timeout.
- -
check_for_updates(logs: ~Logs) -> None
This function runs in a thread indefinitely, it checks the version
-and the latest published version of WebScripts every hour.
-If the version and the latest published version are different,
-this function sends an email notification.
- -
get_owner(filename: str) -> str
This function return the owner of a file.
- -
get_permissions(filename: str) -> str
This function returns the file permissions.
- -
log_rule(rule: hardening.Rule, logs: ~Logs) -> None
This function log rule.
- -
run(server: ~Server) -> List[hardening.Rule]
This function run audit and checks.
- -
-Data descriptors defined here:
-
__dict__
-
dictionary for instance variables (if defined)
-
-
__weakref__
-
list of weak references to the object (if defined)
-
-
-Data and other attributes defined here:
-
current_dir = r'C:\Program Files\Python310'
- -
is_windows = True
- -
latest = []
- -
latest_ = []
- -
network_up = True
- -

- - - - - - - -
 
-class Report(builtins.object)
   Report(rules: List[hardening.Rule], file_integrity: List[Dict[str, str]], server: ~Server)

-This class implement the report object.
 
 Methods defined here:
-
__init__(self, rules: List[hardening.Rule], file_integrity: List[Dict[str, str]], server: ~Server)
Initialize self.  See help(type(self)) for accurate signature.
- -
as_html(self) -> str
This function return a HTML string of audit results.
- -
as_json(self) -> str
This function returns a JSON string of audit results.
- -
as_text(self) -> str
This function return a HTML string of audit results.
- -
get_pourcent(self) -> None
This function calcul pourcent.
- -
notification(self) -> None
This function send an email notification
-to administrator with the audit report.
- -
-Static methods defined here:
-
get_HTML_table(headers: str, rules: List[hardening.Rule]) -> str
This function returns a HTML table with rule attributes as columns.
- -
get_text_table(headers: str, rules: List[hardening.Rule], joiner: str = ' ') -> str
This function return a text table with rule attributes as columns.
- -
truncate_string(string: str, length: int = 13, end: str = '...', separator: str = ',') -> str
This function truncate a string.
- -
-Data descriptors defined here:
-
__dict__
-
dictionary for instance variables (if defined)
-
-
__weakref__
-
list of weak references to the object (if defined)
-
-

- - - - - - - -
 
-class Rule(builtins.object)
   Rule(subject: str, id_: int, is_OK: bool, level: int, severity: str, category: str, reason: str) -&gt; None

-This class implement a rule for hardening.
 
 Methods defined here:
-
__eq__(self, other)
Return self==value.
- -
__init__(self, subject: str, id_: int, is_OK: bool, level: int, severity: str, category: str, reason: str) -> None
Initialize self.  See help(type(self)) for accurate signature.
- -
__repr__(self)
Return repr(self).
- -
-Data descriptors defined here:
-
__dict__
-
dictionary for instance variables (if defined)
-
-
__weakref__
-
list of weak references to the object (if defined)
-
-
-Data and other attributes defined here:
-
__annotations__ = {'category': <class 'str'>, 'id_': <class 'int'>, 'is_OK': <class 'bool'>, 'level': <class 'int'>, 'reason': <class 'str'>, 'severity': <class 'str'>, 'subject': <class 'str'>}
- -
__dataclass_fields__ = {'category': Field(name='category',type=<class 'str'>,default...appingproxy({}),kw_only=False,_field_type=_FIELD), 'id_': Field(name='id_',type=<class 'int'>,default=<dat...appingproxy({}),kw_only=False,_field_type=_FIELD), 'is_OK': Field(name='is_OK',type=<class 'bool'>,default=<...appingproxy({}),kw_only=False,_field_type=_FIELD), 'level': Field(name='level',type=<class 'int'>,default=<d...appingproxy({}),kw_only=False,_field_type=_FIELD), 'reason': Field(name='reason',type=<class 'str'>,default=<...appingproxy({}),kw_only=False,_field_type=_FIELD), 'severity': Field(name='severity',type=<class 'str'>,default...appingproxy({}),kw_only=False,_field_type=_FIELD), 'subject': Field(name='subject',type=<class 'str'>,default=...appingproxy({}),kw_only=False,_field_type=_FIELD)}
- -
__dataclass_params__ = _DataclassParams(init=True,repr=True,eq=True,order=False,unsafe_hash=False,frozen=False)
- -
__hash__ = None
- -
__match_args__ = ('subject', 'id_', 'is_OK', 'level', 'severity', 'category', 'reason')
- -

- - - - - - - -
 
-class SEVERITY(enum.Enum)
   SEVERITY(value, names=None, *, module=None, qualname=None, type=None, start=1)

-Severity level of the rules.
 
 
Method resolution order:
-
SEVERITY
-
enum.Enum
-
builtins.object
-
-
-Data and other attributes defined here:
-
CRITICAL = <SEVERITY.CRITICAL: 'CRITICAL'>
- -
HIGH = <SEVERITY.HIGH: 'HIGH'>
- -
INFORMATION = <SEVERITY.INFORMATION: 'INFORMATION'>
- -
LOW = <SEVERITY.LOW: 'LOW'>
- -
MEDIUM = <SEVERITY.MEDIUM: 'MEDIUM'>
- -
-Data descriptors inherited from enum.Enum:
-
name
-
The name of the Enum member.
-
-
value
-
The value of the Enum member.
-
-
-Readonly properties inherited from enum.EnumMeta:
-
__members__
-
Returns a mapping of member name->value.

-This mapping lists all enum members, including aliases. Note that this
-is a read-only view of the internal mapping.
-
-

- - - - - -
 
-Functions
       
main(server: ~Server) -> hardening.Report
The main function to perform WebScripts Server hardening audit.
-

- - - - - -
 
-Data
       __all__ = ['Report', 'Rule', 'Audit', 'SEVERITY', 'main']
-__author_email__ = 'mauricelambert434@gmail.com'
-__copyright__ = '\nWebScripts Copyright (C) 2021, 2022, 2023 Mau...ome to redistribute it\nunder certain conditions.\n'
-__description__ = '\nThis tool runs CLI scripts and displays output ...f the WebScripts installation and\nconfiguration.\n'
-__license__ = 'GPL-3.0 License'
-__maintainer__ = 'Maurice Lambert'
-__maintainer_email__ = 'mauricelambert434@gmail.com'
-__url__ = 'https://github.com/mauricelambert/WebScripts'

- - - - - -
 
-Author
       Maurice Lambert
- \ No newline at end of file diff --git a/static/html/index.html b/static/html/index.html deleted file mode 100644 index 2110e910..00000000 --- a/static/html/index.html +++ /dev/null @@ -1,282 +0,0 @@ - -Python: module __init__ - - - - - -
 
- 
__init__ (version 3.0.39)
index
__init__.py
-

This tool runs CLI scripts and displays output in a Web Interface.

-

- - - - - -
 
-Classes
       
-
builtins.object -
-
-
WebScripts.Server -
-
-
utils.DefaultNamespace(types.SimpleNamespace) -
-
-
WebScripts.Configuration -
-
-
-

- - - - - - - -
 
-class Configuration(utils.DefaultNamespace)
   Configuration(required: List[str] = [], optional: List[str] = [], default: dict = {}, types: dict = {})

-This class build the configuration from dict(s) with
-configuration files and arguments.
 
 
Method resolution order:
-
Configuration
-
utils.DefaultNamespace
-
types.SimpleNamespace
-
builtins.object
-
-
-Methods defined here:
-
add_conf(self, **kwargs)
Add configurations from other configuration files found.
- -
-Data and other attributes defined here:
-
__defaults__ = {'accept_unauthenticated_user': True, 'accept_unknow_user': True, 'active_auth': False, 'auth_failures_to_blacklist': None, 'auth_script': None, 'blacklist_time': None, 'cgi_path': [], 'documentations_path': [], 'exclude_auth_pages': ['/api/', '/auth/', '/web/auth/'], 'exclude_auth_paths': ['/static/', '/js/'], ...}
- -
__optional__ = ('debug', 'security', 'active_auth', 'auth_script', 'accept_unknow_user', 'accept_unauthenticated_user', 'exclude_auth_paths', 'exclude_auth_pages', 'modules', 'modules_path', 'js_path', 'statics_path', 'documentations_path', 'scripts_path', 'json_scripts_config', 'ini_scripts_config', 'log_level', 'log_filename', 'log_level', 'log_format', ...)
- -
__required__ = ('interface', 'port')
- -
__types__ = {'accept_unauthenticated_user': <class 'bool'>, 'accept_unknow_user': <class 'bool'>, 'active_auth': <class 'bool'>, 'admin_adresses': <class 'list'>, 'admin_groups': typing.List[int], 'auth_failures_to_blacklist': <class 'int'>, 'blacklist_time': <class 'int'>, 'csrf_max_time': <class 'int'>, 'debug': <class 'bool'>, 'documentations_path': <class 'list'>, ...}
- -
-Methods inherited from utils.DefaultNamespace:
-
__getitem__(self, key: str)
Compatibility with dict.
- -
__init__(self, required: List[str] = [], optional: List[str] = [], default: dict = {}, types: dict = {})
Initialize self.  See help(type(self)) for accurate signature.
- -
build_type(self, attribut: str, value: Any, type_: type = None) -> None
This function builds type from configuration value.
- -
build_types(self) -> None
This function builds type from configuration values.
- -
check_required(self) -> None
This function checks required attributes
-if one of required attributes is missing this
-function raise MissingAttributesError.
- -
export_as_json(self, name: str = None) -> None
This function export namespace values (useful for debugging).
- -
get(self, key: str, default=None)
Compatibility with dict.
- -
get_dict(self) -> None
This function return a dict of attributes.
- -
get_missings(self) -> List[str]
This function checks required attributes
-and return a List[str] of missing required attributes.
- -
get_unexpecteds(self, log: bool = True) -> List[str]
This function return a List[str] of
-all attributes not in optional and
-required attributes.

-If log argument is True a Warning log message is
-write for all unexpected attributes.
- -
set_defaults(self) -> None
This function set defaults attribut with defaults values.
- -
update(self, **kwargs)
This function add/update attributes with **kwargs arguments.
- -
-Class methods inherited from utils.DefaultNamespace:
-
default_build(**kwargs) -> ~DefaultNamespace from builtins.type
Default build for DefaultNamespace (set defaults, add values,
-check requirements and unexpected values and build types).
- -
-Data descriptors inherited from utils.DefaultNamespace:
-
__weakref__
-
list of weak references to the object (if defined)
-
-
-Methods inherited from types.SimpleNamespace:
-
__delattr__(self, name, /)
Implement delattr(self, name).
- -
__eq__(self, value, /)
Return self==value.
- -
__ge__(self, value, /)
Return self>=value.
- -
__getattribute__(self, name, /)
Return getattr(self, name).
- -
__gt__(self, value, /)
Return self>value.
- -
__le__(self, value, /)
Return self<=value.
- -
__lt__(self, value, /)
Return self<value.
- -
__ne__(self, value, /)
Return self!=value.
- -
__reduce__(...)
Return state information for pickling
- -
__repr__(self, /)
Return repr(self).
- -
__setattr__(self, name, value, /)
Implement setattr(self, name, value).
- -
-Static methods inherited from types.SimpleNamespace:
-
__new__(*args, **kwargs) from builtins.type
Create and return a new object.  See help(type) for accurate signature.
- -
-Data descriptors inherited from types.SimpleNamespace:
-
__dict__
-
-
-Data and other attributes inherited from types.SimpleNamespace:
-
__hash__ = None
- -

- - - - - - - -
 
-class Server(builtins.object)
   Server(configuration: WebScripts.Configuration)

-This class implements the WebScripts server.
 
 Methods defined here:
-
__init__(self, configuration: WebScripts.Configuration)
Initialize self.  See help(type(self)) for accurate signature.
- -
add_module_or_package(self) -> None
This function add packages and modules to build custom page.
- -
add_paths(self) -> None
This function add js, static and scripts paths.
- -
app(self, environ_: os._Environ, respond: method) -> List[bytes]
This function get function page,
-return content page, catch errors and
-return HTTP errors.
- -
check_auth(self, environ: os._Environ) -> Tuple[commons.User, bool]
This function check if user is authenticated and blacklisted.
- -
check_blacklist(self, user: commons.User, ip: str) -> bool
This function checks that the IP and the
-username are not in the blacklist.
- -
get_URLs(self) -> List[str]
This function return a list of urls (scripts, documentation...)
-and the start of the URL of custom packages.
- -
get_function_page(self, path: str, filename: str) -> Tuple[~FunctionOrNone, str, bool]
This function find function from URL path.
-If the function is a WebScripts built-in function,
-return the function, filename and True. Else return the
-function, filename and False.
- -
get_session(self, cookies: List[str], ip: str) -> commons.User
This function return User from cookies.
- -
page_400(self, environ: os._Environ, user: commons.User, filename: str, method: str, respond: method)
This function return error 400 web page.
- -
page_401(self, environ: os._Environ, user: commons.User, filename: str, error_description: str, respond: method)
This function return error 401 web page.
- -
page_403(self, environ: os._Environ, user: commons.User, filename: str, error_description: str, respond: method)
This function return error 403 web page.
- -
page_404(self, environ: os._Environ, user: commons.User, filename: str, url: str, respond: method)
This function return error 404 web page.
- -
page_406(self, environ: os._Environ, user: commons.User, filename: str, error_description: str, respond: method)
This function return error 406 web page.
- -
page_500(self, environ: os._Environ, user: commons.User, filename: str, error: Union[str, bytes, Iterable[bytes]], respond: method) -> List[bytes]
This function return error 500 web page.
- -
parse_body(self, environ: os._Environ) -> Tuple[~Content, str, bool]
This function returns arguments from body.
- -
return_inputs(self, arguments: List[Dict[str, ~JsonValue]], is_webscripts_request: bool) -> Tuple[List[str], List[str]]
This function returns inputs (using Server.get_inputs).
- -
send_custom_error(self, environ: os._Environ, user: commons.User, filename: str, error: str, code: str) -> Tuple[Optional[str], Optional[Dict[str, str]], Optional[str]]
This function call custom errors pages.
- -
send_error_page(self, environ: os._Environ, user: commons.User, filename: str, error: str, data: bytes, respond: method) -> List[bytes]
This function send HTTP errors.
- -
send_headers(self, environ: os._Environ, respond: method, error: str = None, headers: Dict[str, str] = None) -> None
This function send error code, message and headers.
- -
set_default_values_for_response(self, error: str, headers: Dict[str, str]) -> Tuple[str, Dict[str, str]]
This function returns default error if not defined and
-default headers updated with custom headers.
- -
-Static methods defined here:
-
check_origin(environ_getter: collections.abc.Callable, environ: os._Environ) -> bool
This function checks Origin of POST methods.
- -
get_attributes(object_: object, attributes: List[str], is_not_package: bool = True) -> Tuple[~FunctionOrNone, bool]
This function get recursive attribute from object.
- -
get_baseurl(environ_getter: collections.abc.Callable, environ: os._Environ) -> str
This function returns URL base.
- -
get_content_length(environ: os._Environ) -> int
This function returns the content length.
- -
get_fullurl(environ: os._Environ) -> str
This function returns the full URL (based on the PEP 3333).

-Link: https://peps.python.org/pep-3333/
- -
get_inputs(arguments: List[Dict[str, ~JsonValue]]) -> Tuple[List[str], List[str]]
This function returns inputs and arguments from arguments.
- -
get_json_content(body: bytes, content_type: str) -> ~JsonValue
This functions returns the loaded JSON content.
- -
return_page(page: Union[bytes, str, Iterable[bytes]]) -> List[bytes]
This function returns response as a list of bytes.
- -
set_default_headers(headers: Dict[str, str], security: bool, configuration: WebScripts.Configuration) -> None
This function sets defaults headers.
- -
try_get_command(body: Dict[str, ~JsonValue]) -> Optional[Tuple[~Content, str, bool]]
This function returns arguments, CSRF token and True if is WebScripts
-request. If is not a WebScripts request because there's no "arguments"
-section in request content, this function returns None. If an error
-is raised in arguments parser, this function returns the JSON
-content, None and False.
- -
use_basic_auth(credentials: str, pages: Pages.Pages, *args) -> Tuple[str, Dict[str, str], str]
This function decodes basic auth and
-authenticates user with it.
- -
-Data descriptors defined here:
-
__dict__
-
dictionary for instance variables (if defined)
-
-
__weakref__
-
list of weak references to the object (if defined)
-
-
-Data and other attributes defined here:
-
CommonsClasses = <class 'WebScripts.Server.CommonsClasses'>
- -

- - - - - -
 
-Functions
       
main() -> int
Main function to build the
-configurations and launch the server.
-

- - - - - -
 
-Data
       __all__ = ['Configuration', 'Server', 'main']
-__author_email__ = 'mauricelambert434@gmail.com'
-__copyright__ = '\nWebScripts Copyright (C) 2021, 2022, 2023, 202...ome to redistribute it\nunder certain conditions.\n'
-__description__ = 'This tool runs CLI scripts and displays output in a Web Interface.'
-__license__ = 'GPL-3.0 License'
-__maintainer__ = 'Maurice Lambert'
-__maintainer_email__ = 'mauricelambert434@gmail.com'
-__url__ = 'https://github.com/mauricelambert/WebScripts'

- - - - - -
 
-Author
       Maurice Lambert
- \ No newline at end of file diff --git a/static/html/manage_defaults_databases.html b/static/html/manage_defaults_databases.html deleted file mode 100644 index 5d2c234c..00000000 --- a/static/html/manage_defaults_databases.html +++ /dev/null @@ -1,412 +0,0 @@ - -Python: module manage_defaults_databases - - - - - -
 
- 
manage_defaults_databases (version 2.0.2)
index
manage_defaults_databases.py
-

This tool runs CLI scripts and displays output in a Web Interface.

-This file implement some functions to manage WebScript default databases.

-

- - - - - -
 
-Classes
       
-
builtins.Exception(builtins.BaseException) -
-
-
GroupError -
UserError -
-
-
builtins.tuple(builtins.object) -
-
-
Group -
User -
-
-
-

- - - - - - - -
 
-class Group(builtins.tuple)
   Group(ID, name)

-Group(ID, name)
 
 
Method resolution order:
-
Group
-
builtins.tuple
-
builtins.object
-
-
-Methods defined here:
-
__getnewargs__(self)
Return self as a plain tuple.  Used by copy and pickle.
- -
__repr__(self)
Return a nicely formatted representation string
- -
_asdict(self)
Return a new dict which maps field names to their values.
- -
_replace(self, /, **kwds)
Return a new Group object replacing specified fields with new values
- -
-Class methods defined here:
-
_make(iterable) from builtins.type
Make a new Group object from a sequence or iterable
- -
-Static methods defined here:
-
__new__(_cls, ID, name)
Create new instance of Group(ID, name)
- -
-Data descriptors defined here:
-
ID
-
Alias for field number 0
-
-
name
-
Alias for field number 1
-
-
-Data and other attributes defined here:
-
__match_args__ = ('ID', 'name')
- -
_field_defaults = {}
- -
_fields = ('ID', 'name')
- -
-Methods inherited from builtins.tuple:
-
__add__(self, value, /)
Return self+value.
- -
__contains__(self, key, /)
Return key in self.
- -
__eq__(self, value, /)
Return self==value.
- -
__ge__(self, value, /)
Return self>=value.
- -
__getattribute__(self, name, /)
Return getattr(self, name).
- -
__getitem__(self, key, /)
Return self[key].
- -
__gt__(self, value, /)
Return self>value.
- -
__hash__(self, /)
Return hash(self).
- -
__iter__(self, /)
Implement iter(self).
- -
__le__(self, value, /)
Return self<=value.
- -
__len__(self, /)
Return len(self).
- -
__lt__(self, value, /)
Return self<value.
- -
__mul__(self, value, /)
Return self*value.
- -
__ne__(self, value, /)
Return self!=value.
- -
__rmul__(self, value, /)
Return value*self.
- -
count(self, value, /)
Return number of occurrences of value.
- -
index(self, value, start=0, stop=9223372036854775807, /)
Return first index of value.

-Raises ValueError if the value is not present.
- -
-Class methods inherited from builtins.tuple:
-
__class_getitem__(...) from builtins.type
See PEP 585
- -

- - - - - - - -
 
-class GroupError(builtins.Exception)
   This class can raise a GroupError.
 
 
Method resolution order:
-
GroupError
-
builtins.Exception
-
builtins.BaseException
-
builtins.object
-
-
-Data descriptors defined here:
-
__weakref__
-
list of weak references to the object (if defined)
-
-
-Methods inherited from builtins.Exception:
-
__init__(self, /, *args, **kwargs)
Initialize self.  See help(type(self)) for accurate signature.
- -
-Static methods inherited from builtins.Exception:
-
__new__(*args, **kwargs) from builtins.type
Create and return a new object.  See help(type) for accurate signature.
- -
-Methods inherited from builtins.BaseException:
-
__delattr__(self, name, /)
Implement delattr(self, name).
- -
__getattribute__(self, name, /)
Return getattr(self, name).
- -
__reduce__(...)
Helper for pickle.
- -
__repr__(self, /)
Return repr(self).
- -
__setattr__(self, name, value, /)
Implement setattr(self, name, value).
- -
__setstate__(...)
- -
__str__(self, /)
Return str(self).
- -
with_traceback(...)
Exception.with_traceback(tb) --
-set self.__traceback__ to tb and return self.
- -
-Data descriptors inherited from builtins.BaseException:
-
__cause__
-
exception cause
-
-
__context__
-
exception context
-
-
__dict__
-
-
__suppress_context__
-
-
__traceback__
-
-
args
-
-

- - - - - - - -
 
-class User(builtins.tuple)
   User(ID, name, password, salt, enumerations, IPs, groups, apikey, categories, scripts)

-User(ID, name, password, salt, enumerations, IPs, groups, apikey, categories, scripts)
 
 
Method resolution order:
-
User
-
builtins.tuple
-
builtins.object
-
-
-Methods defined here:
-
__getnewargs__(self)
Return self as a plain tuple.  Used by copy and pickle.
- -
__repr__(self)
Return a nicely formatted representation string
- -
_asdict(self)
Return a new dict which maps field names to their values.
- -
_replace(self, /, **kwds)
Return a new User object replacing specified fields with new values
- -
-Class methods defined here:
-
_make(iterable) from builtins.type
Make a new User object from a sequence or iterable
- -
-Static methods defined here:
-
__new__(_cls, ID, name, password, salt, enumerations, IPs, groups, apikey, categories, scripts)
Create new instance of User(ID, name, password, salt, enumerations, IPs, groups, apikey, categories, scripts)
- -
-Data descriptors defined here:
-
ID
-
Alias for field number 0
-
-
name
-
Alias for field number 1
-
-
password
-
Alias for field number 2
-
-
salt
-
Alias for field number 3
-
-
enumerations
-
Alias for field number 4
-
-
IPs
-
Alias for field number 5
-
-
groups
-
Alias for field number 6
-
-
apikey
-
Alias for field number 7
-
-
categories
-
Alias for field number 8
-
-
scripts
-
Alias for field number 9
-
-
-Data and other attributes defined here:
-
__match_args__ = ('ID', 'name', 'password', 'salt', 'enumerations', 'IPs', 'groups', 'apikey', 'categories', 'scripts')
- -
_field_defaults = {}
- -
_fields = ('ID', 'name', 'password', 'salt', 'enumerations', 'IPs', 'groups', 'apikey', 'categories', 'scripts')
- -
-Methods inherited from builtins.tuple:
-
__add__(self, value, /)
Return self+value.
- -
__contains__(self, key, /)
Return key in self.
- -
__eq__(self, value, /)
Return self==value.
- -
__ge__(self, value, /)
Return self>=value.
- -
__getattribute__(self, name, /)
Return getattr(self, name).
- -
__getitem__(self, key, /)
Return self[key].
- -
__gt__(self, value, /)
Return self>value.
- -
__hash__(self, /)
Return hash(self).
- -
__iter__(self, /)
Implement iter(self).
- -
__le__(self, value, /)
Return self<=value.
- -
__len__(self, /)
Return len(self).
- -
__lt__(self, value, /)
Return self<value.
- -
__mul__(self, value, /)
Return self*value.
- -
__ne__(self, value, /)
Return self!=value.
- -
__rmul__(self, value, /)
Return value*self.
- -
count(self, value, /)
Return number of occurrences of value.
- -
index(self, value, start=0, stop=9223372036854775807, /)
Return first index of value.

-Raises ValueError if the value is not present.
- -
-Class methods inherited from builtins.tuple:
-
__class_getitem__(...) from builtins.type
See PEP 585
- -

- - - - - - - -
 
-class UserError(builtins.Exception)
   This class can raise a UserError.
 
 
Method resolution order:
-
UserError
-
builtins.Exception
-
builtins.BaseException
-
builtins.object
-
-
-Data descriptors defined here:
-
__weakref__
-
list of weak references to the object (if defined)
-
-
-Methods inherited from builtins.Exception:
-
__init__(self, /, *args, **kwargs)
Initialize self.  See help(type(self)) for accurate signature.
- -
-Static methods inherited from builtins.Exception:
-
__new__(*args, **kwargs) from builtins.type
Create and return a new object.  See help(type) for accurate signature.
- -
-Methods inherited from builtins.BaseException:
-
__delattr__(self, name, /)
Implement delattr(self, name).
- -
__getattribute__(self, name, /)
Return getattr(self, name).
- -
__reduce__(...)
Helper for pickle.
- -
__repr__(self, /)
Return repr(self).
- -
__setattr__(self, name, value, /)
Implement setattr(self, name, value).
- -
__setstate__(...)
- -
__str__(self, /)
Return str(self).
- -
with_traceback(...)
Exception.with_traceback(tb) --
-set self.__traceback__ to tb and return self.
- -
-Data descriptors inherited from builtins.BaseException:
-
__cause__
-
exception cause
-
-
__context__
-
exception context
-
-
__dict__
-
-
__suppress_context__
-
-
__traceback__
-
-
args
-
-

- - - - - -
 
-Functions
       
add_group(name: str, id_: int) -> manage_defaults_databases.Group
This function add group in database or raise a GroupError.
-
add_user(name: str, password: str, groups: List[int], ips: List[str] = ['*'], categories: List[str] = ['*'], scripts: List[str] = ['*']) -> manage_defaults_databases.User
This function add user in database or raise a UserError.
-
change_user_password(id_: str, new_password: str, old_password: str = None) -> manage_defaults_databases.User
This function change a user password.
-
create_default_databases() -> None
This function create defaults users and groups.
-
delete_group(id_: int) -> manage_defaults_databases.Group
This function delete a group by id and return the deleted group.
-
delete_user(id_: int) -> manage_defaults_databases.User
This function delete a user by id and return the deleted user.
-
get_apikey(id_: str, password: str) -> str
This function returns the API key after verification of authentication.
-
get_dict_groups(by_name: bool = False) -> Dict[str, str]
This function returns the dict of groups (ID: Name or Name: ID).
-
get_groups() -> collections.abc.Iterator[manage_defaults_databases.Group]
This function get groups from CSV database.
-
get_users() -> collections.abc.Iterator[manage_defaults_databases.User]
This function get users from CSV database.
-

- - - - - -
 
-Data
       __all__ = ['User', 'Group', 'add_user', 'add_group', 'get_users', 'UserError', 'GroupError', 'get_groups', 'get_apikey', 'delete_user', 'delete_group', 'get_dict_groups', 'change_user_password', 'create_default_databases']
-__author_email__ = 'mauricelambert434@gmail.com'
-__copyright__ = '\nWebScripts Copyright (C) 2021, 2022, 2023 Mau...ome to redistribute it\nunder certain conditions.\n'
-__description__ = '\nThis tool runs CLI scripts and displays output ...ctions to manage WebScript default user database\n'
-__license__ = 'GPL-3.0 License'
-__maintainer__ = 'Maurice Lambert'
-__maintainer_email__ = 'mauricelambert434@gmail.com'
-__url__ = 'https://github.com/mauricelambert/WebScripts'
-__warningregistry__ = {'version': 0}

- - - - - -
 
-Author
       Maurice Lambert
- \ No newline at end of file diff --git a/static/html/notification.html b/static/html/notification.html deleted file mode 100644 index dd12d169..00000000 --- a/static/html/notification.html +++ /dev/null @@ -1,47 +0,0 @@ - -Python: module notification - - - - - -
 
- 
notification (version 0.0.1)
index
notification.py
-

This tool run scripts and display the result in a Web Interface.

-This module adds notifications on WebScripts pages.

-

- - - - - -
 
-Functions
       
add(environ: os._Environ, user: ~User, server: ~Server, filename: str, arguments: List[str], inputs: List[str], csrf_token: str = None) -> Tuple[str, Dict[str, str], str]
This function adds a new notification
-in WebScripts pages.
-

- - - - - -
 
-Data
       __all__ = ['add']
-__annotations__ = {'NEW_LINE': <class 'str'>, 'notification_id': <class 'int'>}
-__author_email__ = 'mauricelambert434@gmail.com'
-__copyright__ = '\nWebScripts Copyright (C) 2021, 2022 Maurice L...ome to redistribute it\nunder certain conditions.\n'
-__description__ = '\nThis tool run scripts and display the result in...s module adds notifications on WebScripts pages.\n'
-__license__ = 'GPL-3.0 License'
-__maintainer__ = 'Maurice Lambert'
-__maintainer_email__ = 'mauricelambert434@gmail.com'
-__url__ = 'https://github.com/mauricelambert/WebScripts'

- - - - - -
 
-Author
       Maurice Lambert
- \ No newline at end of file diff --git a/static/html/requests_management.html b/static/html/requests_management.html deleted file mode 100644 index 1d9a26a5..00000000 --- a/static/html/requests_management.html +++ /dev/null @@ -1,184 +0,0 @@ - -Python: module requests_management - - - - - -
 
- 
requests_management (version 0.1.0)
index
requests_management.py
-

#    This tool runs CLI scripts and displays output in a Web Interface.

-This file implement some functions to manage requests/reports on WebScripts.

-

- - - - - -
 
-Modules
       
csv
-
ntpath
-

- - - - - -
 
-Classes
       
-
builtins.tuple(builtins.object) -
-
-
Request -
-
-
-

- - - - - - - -
 
-class Request(builtins.tuple)
   Request(ID, Time, UserName, ErrorCode, Page, UserAgent, Subject, Reason, Name)

-Request(ID, Time, UserName, ErrorCode, Page, UserAgent, Subject, Reason, Name)
 
 
Method resolution order:
-
Request
-
builtins.tuple
-
builtins.object
-
-
-Methods defined here:
-
__getnewargs__(self)
Return self as a plain tuple.  Used by copy and pickle.
- -
__repr__(self)
Return a nicely formatted representation string
- -
_asdict(self)
Return a new dict which maps field names to their values.
- -
_replace(self, /, **kwds)
Return a new Request object replacing specified fields with new values
- -
-Class methods defined here:
-
_make(iterable) from builtins.type
Make a new Request object from a sequence or iterable
- -
-Static methods defined here:
-
__new__(_cls, ID, Time, UserName, ErrorCode, Page, UserAgent, Subject, Reason, Name)
Create new instance of Request(ID, Time, UserName, ErrorCode, Page, UserAgent, Subject, Reason, Name)
- -
-Data descriptors defined here:
-
ID
-
Alias for field number 0
-
-
Time
-
Alias for field number 1
-
-
UserName
-
Alias for field number 2
-
-
ErrorCode
-
Alias for field number 3
-
-
Page
-
Alias for field number 4
-
-
UserAgent
-
Alias for field number 5
-
-
Subject
-
Alias for field number 6
-
-
Reason
-
Alias for field number 7
-
-
Name
-
Alias for field number 8
-
-
-Data and other attributes defined here:
-
__match_args__ = ('ID', 'Time', 'UserName', 'ErrorCode', 'Page', 'UserAgent', 'Subject', 'Reason', 'Name')
- -
_field_defaults = {}
- -
_fields = ('ID', 'Time', 'UserName', 'ErrorCode', 'Page', 'UserAgent', 'Subject', 'Reason', 'Name')
- -
-Methods inherited from builtins.tuple:
-
__add__(self, value, /)
Return self+value.
- -
__contains__(self, key, /)
Return key in self.
- -
__eq__(self, value, /)
Return self==value.
- -
__ge__(self, value, /)
Return self>=value.
- -
__getattribute__(self, name, /)
Return getattr(self, name).
- -
__getitem__(self, key, /)
Return self[key].
- -
__gt__(self, value, /)
Return self>value.
- -
__hash__(self, /)
Return hash(self).
- -
__iter__(self, /)
Implement iter(self).
- -
__le__(self, value, /)
Return self<=value.
- -
__len__(self, /)
Return len(self).
- -
__lt__(self, value, /)
Return self<value.
- -
__mul__(self, value, /)
Return self*value.
- -
__ne__(self, value, /)
Return self!=value.
- -
__rmul__(self, value, /)
Return value*self.
- -
count(self, value, /)
Return number of occurrences of value.
- -
index(self, value, start=0, stop=9223372036854775807, /)
Return first index of value.

-Raises ValueError if the value is not present.
- -
-Class methods inherited from builtins.tuple:
-
__class_getitem__(...) from builtins.type
See PEP 585
- -

- - - - - -
 
-Functions
       
delete_request(id_: str) -> requests_management.Request
This function rewrite the request database without the specified request.
-
get_request(id_: str) -> requests_management.Request
This function return a specific request.
-
get_requests() -> collections.abc.Iterator[requests_management.Request]
This function build Request from database.
-

- - - - - -
 
-Data
       __all__ = ['Request', 'get_requests', 'get_request', 'delete_request']
-__author_email__ = 'mauricelambert434@gmail.com'
-__copyright__ = '\nWebScripts Copyright (C) 2021, 2022, 2023 Mau...ome to redistribute it\nunder certain conditions.\n'
-__description__ = '\nThis tool runs CLI scripts and displays output ...nctions to manage requests/reports on WebScripts\n'
-__license__ = 'GPL-3.0 License'
-__maintainer__ = 'Maurice Lambert'
-__maintainer_email__ = 'mauricelambert434@gmail.com'
-__url__ = 'https://github.com/mauricelambert/WebScripts'

- - - - - -
 
-Author
       Maurice Lambert
- \ No newline at end of file diff --git a/static/html/rss.html b/static/html/rss.html deleted file mode 100644 index 3592712d..00000000 --- a/static/html/rss.html +++ /dev/null @@ -1,208 +0,0 @@ - -Python: module rss - - - - - -
 
- 
rss (version 0.0.1)
index
rss.py
-

This tool runs CLI scripts and displays output in a Web Interface.

-This file implements a RSS feed.

-

- - - - - -
 
-Classes
       
-
builtins.object -
-
-
Feed -
-
-
enum.Enum(builtins.object) -
-
-
FIELDS -
-
-
-

- - - - - - - -
 
-class FIELDS(enum.Enum)
   FIELDS(value, names=None, *, module=None, qualname=None, type=None, start=1)

-An enumeration.
 
 
Method resolution order:
-
FIELDS
-
enum.Enum
-
builtins.object
-
-
-Data and other attributes defined here:
-
author = <FIELDS.author: 1>
- -
categories = <FIELDS.categories: 5>
- -
comments = <FIELDS.comments: 7>
- -
description = <FIELDS.description: 3>
- -
guid = <FIELDS.guid: 0>
- -
link = <FIELDS.link: 4>
- -
pubDate = <FIELDS.pubDate: 6>
- -
title = <FIELDS.title: 2>
- -
-Data descriptors inherited from enum.Enum:
-
name
-
The name of the Enum member.
-
-
value
-
The value of the Enum member.
-
-
-Readonly properties inherited from enum.EnumMeta:
-
__members__
-
Returns a mapping of member name->value.

-This mapping lists all enum members, including aliases. Note that this
-is a read-only view of the internal mapping.
-
-

- - - - - - - -
 
-class Feed(builtins.object)
   Feed(environ: os._Environ, user: ~User, server: ~Server, category: str, arguments: Dict[str, Dict[str, str]], inputs: List[str], csrf_token: str = None) -&gt; Tuple[str, Dict[str, str], Iterable[bytes]]

-This class implements the RSS feed.
 
 Class methods defined here:
-
csv(environ: os._Environ, user: ~User, server: ~Server, category: str, arguments: Dict[str, Dict[str, str]], inputs: List[str], csrf_token: str = None) -> Tuple[str, Dict[str, str], str] from builtins.type
This function prints the RSS feed content as CSV.
- -
generator(full_url: str, base_url: str, rss_path: str, category: str) -> Iterable[bytes] from builtins.type
This generator returns RSS feed content.
- -
json(environ: os._Environ, user: ~User, server: ~Server, category: str, arguments: Dict[str, Dict[str, str]], inputs: List[str], csrf_token: str = None) -> Tuple[str, Dict[str, str], str] from builtins.type
This function prints the RSS feed content as JSON.
- -
-Static methods defined here:
-
__new__(cls: type, environ: os._Environ, user: ~User, server: ~Server, category: str, arguments: Dict[str, Dict[str, str]], inputs: List[str], csrf_token: str = None) -> Tuple[str, Dict[str, str], Iterable[bytes]]
This function prints the RSS feed.
- -
-Data descriptors defined here:
-
__dict__
-
dictionary for instance variables (if defined)
-
-
__weakref__
-
list of weak references to the object (if defined)
-
-
-Data and other attributes defined here:
-
__annotations__ = {'default': typing.List[str], 'optional': typing.List[str], 'required': typing.List[str], 'rss_path': <class 'str'>}
- -
default = ['author', 'guid', 'pubDate', 'lastBuildDate']
- -
optional = ['comments']
- -
required = ['title', 'description', 'link', 'categories']
- -
rss_path = None
- -

- - - - - -
 
-Functions
       
get_rss_path(server: ~Server) -> str
This function returns the rss path.
-
localtime(...)
localtime([seconds]) -> (tm_year,tm_mon,tm_mday,tm_hour,tm_min,
-                          tm_sec,tm_wday,tm_yday,tm_isdst)

-Convert seconds since the Epoch to a time tuple expressing local time.
-When 'seconds' is not passed in, convert the current time instead.
-
strftime(...)
strftime(format[, tuple]) -> string

-Convert a time tuple to a string according to a format specification.
-See the library reference manual for formatting codes. When the time tuple
-is not present, current time as returned by localtime() is used.

-Commonly used format codes:

-%Y  Year with century as a decimal number.
-%m  Month as a decimal number [01,12].
-%d  Day of the month as a decimal number [01,31].
-%H  Hour (24-hour clock) as a decimal number [00,23].
-%M  Minute as a decimal number [00,59].
-%S  Second as a decimal number [00,61].
-%z  Time zone offset from UTC.
-%a  Locale's abbreviated weekday name.
-%A  Locale's full weekday name.
-%b  Locale's abbreviated month name.
-%B  Locale's full month name.
-%c  Locale's appropriate date and time representation.
-%I  Hour (12-hour clock) as a decimal number [01,12].
-%p  Locale's equivalent of either AM or PM.

-Other codes may be available on your platform.  See documentation for
-the C library strftime function.
-
writer(...)
csv_writer = csv.writer(fileobj [, dialect='excel']
-                            [optional keyword args])
-    for row in sequence:
-        csv_writer.writerow(row)

-    [or]

-    csv_writer = csv.writer(fileobj [, dialect='excel']
-                            [optional keyword args])
-    csv_writer.writerows(rows)

-The "fileobj" argument can be any object that supports the file API.
-

- - - - - -
 
-Data
       Dict = typing.Dict
-Iterable = typing.Iterable
-List = typing.List
-QUOTE_ALL = 1
-Server = ~Server
-Tuple = typing.Tuple
-User = ~User
-__author_email__ = 'mauricelambert434@gmail.com'
-__copyright__ = '\nWebScripts Copyright (C) 2021, 2022, 2023 Mau...ome to redistribute it\nunder certain conditions.\n'
-__description__ = '\nThis tool runs CLI scripts and displays output ...Web Interface.\n\nThis file implements a RSS feed.\n'
-__license__ = 'GPL-3.0 License'
-__maintainer__ = 'Maurice Lambert'
-__maintainer_email__ = 'mauricelambert434@gmail.com'
-__url__ = 'https://github.com/mauricelambert/WebScripts'
-copyright = '\nWebScripts Copyright (C) 2021, 2022, 2023 Mau...ome to redistribute it\nunder certain conditions.\n'
-license = 'GPL-3.0 License'

- - - - - -
 
-Author
       Maurice Lambert
- \ No newline at end of file diff --git a/static/html/share.html b/static/html/share.html deleted file mode 100644 index 0938cec4..00000000 --- a/static/html/share.html +++ /dev/null @@ -1,103 +0,0 @@ - -Python: module share - - - - - -
 
- 
share (version 0.1.0)
index
share.py
-

This tool run scripts and display the result in a Web Interface.

-This file download and upload functions for scripts,
-tools and command line client.

-

- - - - - -
 
-Modules
       
ntpath
-

- - - - - -
 
-Classes
       
-
builtins.object -
-
-
Download -
-
-
-

- - - - - - - -
 
-class Download(builtins.object)
   This class implement download functions for filename or ID.
 
 Methods defined here:
-
filename(environ: os._Environ, user: ~User, server_configuration: ~ServerConfiguration, filename: str, arguments: List[str], inputs: List[str], csrf_token: str = None) -> Tuple[str, Dict[str, str], str]
This funtion download file by filename.
- -
id(environ: os._Environ, user: ~User, server_configuration: ~ServerConfiguration, id_: str, arguments: List[str], inputs: List[str], csrf_token: str = None) -> Tuple[str, Dict[str, str], str]
This funtion download file by ID.
- -
-Static methods defined here:
-
get_data(file: uploads_management.Upload) -> bytes
This function get file and manage the compression.
- -
-Data descriptors defined here:
-
__dict__
-
dictionary for instance variables (if defined)
-
-
__weakref__
-
list of weak references to the object (if defined)
-
-

- - - - - -
 
-Functions
       
upload(environ: os._Environ, user: ~User, server_configuration: ~ServerConfiguration, filename: str, data: bytes, none: None, csrf_token: str = None) -> Tuple[str, Dict[str, str], str]
This funtion upload file.
-

- - - - - -
 
-Data
       Dict = typing.Dict
-List = typing.List
-ServerConfiguration = ~ServerConfiguration
-Tuple = typing.Tuple
-User = ~User
-__author_email__ = 'mauricelambert434@gmail.com'
-__copyright__ = '\nWebScripts Copyright (C) 2021, 2022 Maurice L...ome to redistribute it\nunder certain conditions.\n'
-__description__ = '\nThis tool run scripts and display the result in...tions for scripts,\ntools and command line client.'
-__license__ = 'GPL-3.0 License'
-__maintainer__ = 'Maurice Lambert'
-__maintainer_email__ = 'mauricelambert434@gmail.com'
-__url__ = 'https://github.com/mauricelambert/WebScripts'
-copyright = '\nWebScripts Copyright (C) 2021, 2022 Maurice L...ome to redistribute it\nunder certain conditions.\n'
-env = environ({'ALLUSERSPROFILE': 'C:\\ProgramData', '...\\WebScripts\\go\\bin', '_OLD_VIRTUAL_PROMPT': '$P$G'})
-license = 'GPL-3.0 License'
-syspath = [r'/home/WebScripts\scripts\doc', r'C:\Program Files\Python\python39.zip', r'C:\Program Files\Python\DLLs', r'C:\Program Files\Python\lib', r'C:\Program Files\Python', r'C:\Users\WebScripts\Documents\dev\test', r'C:\Users\WebScripts\Documents\dev\test\lib\site-packages', r'c:\users\WebScripts\documents\dev\modules\webscripts', r'C:\Users\WebScripts\Documents\dev\test\lib\site-packages\win32', r'C:\Users\WebScripts\Documents\dev\test\lib\site-packages\win32\lib', r'C:\Users\WebScripts\Documents\dev\test\lib\site-packages\Pythonwin', r'/home/WebScripts\modules']

- - - - - -
 
-Author
       Maurice Lambert
- \ No newline at end of file diff --git a/static/html/uploads_management.html b/static/html/uploads_management.html deleted file mode 100644 index 76f4579c..00000000 --- a/static/html/uploads_management.html +++ /dev/null @@ -1,266 +0,0 @@ - -Python: module uploads_management - - - - - -
 
- 
uploads_management (version 1.0.0)
index
uploads_management.py
-

This tool runs CLI scripts and displays output in a Web Interface.

-This file implement some functions to manage uploads on WebScripts.

-

- - - - - -
 
-Classes
       
-
builtins.object -
-
-
FileMetadata -
UploadedFile -
-
-
builtins.tuple(builtins.object) -
-
-
Upload -
-
-
-

- - - - - - - -
 
-class FileMetadata(builtins.object)
   This class implements file metadata for
-uploaded files.
 
 Methods defined here:
-
__init__(self)
Initialize self.  See help(type(self)) for accurate signature.
- -
add(self, stat: os.stat_result, timestamp: float)
This function add a version to file metadata.
- -
-Data descriptors defined here:
-
__dict__
-
dictionary for instance variables (if defined)
-
-
__weakref__
-
list of weak references to the object (if defined)
-
-

- - - - - - - -
 
-class Upload(builtins.tuple)
   Upload(ID, name, read_permission, write_permission, delete_permission, hidden, is_deleted, is_binary, no_compression, timestamp, user, version)

-Upload(ID, name, read_permission, write_permission, delete_permission, hidden, is_deleted, is_binary, no_compression, timestamp, user, version)
 
 
Method resolution order:
-
Upload
-
builtins.tuple
-
builtins.object
-
-
-Methods defined here:
-
__getnewargs__(self)
Return self as a plain tuple.  Used by copy and pickle.
- -
__repr__(self)
Return a nicely formatted representation string
- -
_asdict(self)
Return a new dict which maps field names to their values.
- -
_replace(self, /, **kwds)
Return a new Upload object replacing specified fields with new values
- -
-Class methods defined here:
-
_make(iterable) from builtins.type
Make a new Upload object from a sequence or iterable
- -
-Static methods defined here:
-
__new__(_cls, ID, name, read_permission, write_permission, delete_permission, hidden, is_deleted, is_binary, no_compression, timestamp, user, version)
Create new instance of Upload(ID, name, read_permission, write_permission, delete_permission, hidden, is_deleted, is_binary, no_compression, timestamp, user, version)
- -
-Data descriptors defined here:
-
ID
-
Alias for field number 0
-
-
name
-
Alias for field number 1
-
-
read_permission
-
Alias for field number 2
-
-
write_permission
-
Alias for field number 3
-
-
delete_permission
-
Alias for field number 4
-
-
hidden
-
Alias for field number 5
-
-
is_deleted
-
Alias for field number 6
-
-
is_binary
-
Alias for field number 7
-
-
no_compression
-
Alias for field number 8
-
-
timestamp
-
Alias for field number 9
-
-
user
-
Alias for field number 10
-
-
version
-
Alias for field number 11
-
-
-Data and other attributes defined here:
-
__match_args__ = ('ID', 'name', 'read_permission', 'write_permission', 'delete_permission', 'hidden', 'is_deleted', 'is_binary', 'no_compression', 'timestamp', 'user', 'version')
- -
_field_defaults = {}
- -
_fields = ('ID', 'name', 'read_permission', 'write_permission', 'delete_permission', 'hidden', 'is_deleted', 'is_binary', 'no_compression', 'timestamp', 'user', 'version')
- -
-Methods inherited from builtins.tuple:
-
__add__(self, value, /)
Return self+value.
- -
__contains__(self, key, /)
Return key in self.
- -
__eq__(self, value, /)
Return self==value.
- -
__ge__(self, value, /)
Return self>=value.
- -
__getattribute__(self, name, /)
Return getattr(self, name).
- -
__getitem__(self, key, /)
Return self[key].
- -
__gt__(self, value, /)
Return self>value.
- -
__hash__(self, /)
Return hash(self).
- -
__iter__(self, /)
Implement iter(self).
- -
__le__(self, value, /)
Return self<=value.
- -
__len__(self, /)
Return len(self).
- -
__lt__(self, value, /)
Return self<value.
- -
__mul__(self, value, /)
Return self*value.
- -
__ne__(self, value, /)
Return self!=value.
- -
__rmul__(self, value, /)
Return value*self.
- -
count(self, value, /)
Return number of occurrences of value.
- -
index(self, value, start=0, stop=9223372036854775807, /)
Return first index of value.

-Raises ValueError if the value is not present.
- -
-Class methods inherited from builtins.tuple:
-
__class_getitem__(...) from builtins.type
See PEP 585
- -

- - - - - - - -
 
-class UploadedFile(builtins.object)
   UploadedFile(name: str, read_access: int, write_access: int, delete_access: int, hidden: bool, binary: bool, no_compression: bool, with_access: bool = True)

-This class implements the file type for
-uploaded files.
 
 Methods defined here:
-
__del__(self, *args, **kwargs)
- -
__enter__(self, *args, **kwargs)
- -
__exit__(self, *args, **kwargs)
- -
__getattr__(self, attr: str)
- -
__init__(self, name: str, read_access: int, write_access: int, delete_access: int, hidden: bool, binary: bool, no_compression: bool, with_access: bool = True)
Initialize self.  See help(type(self)) for accurate signature.
- -
__iter__(self, *args, **kwargs)
- -
__next__(self, *args, **kwargs)
- -
-Data descriptors defined here:
-
__dict__
-
dictionary for instance variables (if defined)
-
-
__weakref__
-
list of weak references to the object (if defined)
-
-

- - - - - -
 
-Functions
       
delete_file(name: str) -> uploads_management.Upload
This function delete an uploaded file.
-
get_file(name: str, id_: str = None) -> Tuple[List[uploads_management.Upload], collections.Counter]
This function return the history of a file.

-If name is None, this function get Upload by ID.
-
get_file_content(name: str = None, id_: str = None) -> Tuple[str, str]
This function return a base64 of the file
-content and the filename (without check permissions).

-If id_ and name arguments are None this function return (None, None).

-Using a name this function return the last versions of the file content.
-Using an ID this function return the version of this ID.
-
get_metadata() -> Dict[str, uploads_management.FileMetadata]
This function returns metadata of
-each uploaded files and versions.
-
get_reader(file: uploads_management.Upload) -> _io._TextIOBase
This function returns a reader
-of the uploaded file.
-
get_visible_files() -> collections.abc.Iterator[uploads_management.Upload]
This function return upload if not hidden.
-
read_file(name: str) -> str
This function check permission and
-return a base64 of the file content.
-
write_file(data: str, name: str, read_access: int, write_access: int, delete_access: int, hidden: bool, binary: bool, no_compression: bool, is_b64: bool, with_access: bool = True) -> uploads_management.Upload
This function uploads a file.
-

- - - - - -
 
-Data
       __all__ = ['Upload', 'get_file', 'read_file', 'write_file', 'get_reader', 'delete_file', 'get_metadata', 'FileMetadata', 'UploadedFile', 'get_file_content', 'get_visible_files']
-__author_email__ = 'mauricelambert434@gmail.com'
-__copyright__ = '\nWebScripts Copyright (C) 2021, 2022, 2023 Mau...ome to redistribute it\nunder certain conditions.\n'
-__description__ = '\nThis tool runs CLI scripts and displays output ...t some functions to manage uploads on WebScripts\n'
-__license__ = 'GPL-3.0 License'
-__maintainer__ = 'Maurice Lambert'
-__maintainer_email__ = 'mauricelambert434@gmail.com'
-__url__ = 'https://github.com/mauricelambert/WebScripts'
-__warningregistry__ = {'version': 0}

- - - - - -
 
-Author
       Maurice Lambert
- \ No newline at end of file diff --git a/static/html/utils.html b/static/html/utils.html deleted file mode 100644 index 26fe3d91..00000000 --- a/static/html/utils.html +++ /dev/null @@ -1,439 +0,0 @@ - -Python: module utils - - - - - -
 
- 
utils (version 1.0.2)
index
utils.py
-

This tool runs CLI scripts and displays output in a Web Interface.

-This file implements some tools for WebScripts server
-and scripts (Logs, Namespace for configuration, ...).

-

- - - - - -
 
-Modules
       
logging
-
ntpath
-

- - - - - -
 
-Classes
       
-
builtins.object -
-
-
_Logs -
-
-
logging.handlers.RotatingFileHandler(logging.handlers.BaseRotatingHandler) -
-
-
CustomLogHandler -
-
-
types.SimpleNamespace(builtins.object) -
-
-
DefaultNamespace -
-
-
-

- - - - - - - -
 
-class CustomLogHandler(logging.handlers.RotatingFileHandler)
   CustomLogHandler(filename, mode='a', maxBytes=0, backupCount=0, encoding=None, delay=False, errors=None)

-This class is a custom logging handler.
 
 
Method resolution order:
-
CustomLogHandler
-
logging.handlers.RotatingFileHandler
-
logging.handlers.BaseRotatingHandler
-
logging.FileHandler
-
logging.StreamHandler
-
logging.Handler
-
logging.Filterer
-
builtins.object
-
-
-Methods defined here:
-
doRollover(self)
Do a rollover, as described in __init__().
- -
namer(self, name: str) -> str
This function returns the new name of the old log files.
- -
rotator(self, source: str, destination: str) -> None
This function compresses old log files.
- -
-Methods inherited from logging.handlers.RotatingFileHandler:
-
__init__(self, filename, mode='a', maxBytes=0, backupCount=0, encoding=None, delay=False, errors=None)
Open the specified file and use it as the stream for logging.

-By default, the file grows indefinitely. You can specify particular
-values of maxBytes and backupCount to allow the file to rollover at
-a predetermined size.

-Rollover occurs whenever the current log file is nearly maxBytes in
-length. If backupCount is >= 1, the system will successively create
-new files with the same pathname as the base file, but with extensions
-".1", ".2" etc. appended to it. For example, with a backupCount of 5
-and a base file name of "app.log", you would get "app.log",
-"app.log.1", "app.log.2", ... through to "app.log.5". The file being
-written to is always "app.log" - when it gets filled up, it is closed
-and renamed to "app.log.1", and if files "app.log.1", "app.log.2" etc.
-exist, then they are renamed to "app.log.2", "app.log.3" etc.
-respectively.

-If maxBytes is zero, rollover never occurs.
- -
shouldRollover(self, record)
Determine if rollover should occur.

-Basically, see if the supplied record would cause the file to exceed
-the size limit we have.
- -
-Methods inherited from logging.handlers.BaseRotatingHandler:
-
emit(self, record)
Emit a record.

-Output the record to the file, catering for rollover as described
-in doRollover().
- -
rotate(self, source, dest)
When rotating, rotate the current log.

-The default implementation calls the 'rotator' attribute of the
-handler, if it's callable, passing the source and dest arguments to
-it. If the attribute isn't callable (the default is None), the source
-is simply renamed to the destination.

-:param source: The source filename. This is normally the base
-               filename, e.g. 'test.log'
-:param dest:   The destination filename. This is normally
-               what the source is rotated to, e.g. 'test.log.1'.
- -
rotation_filename(self, default_name)
Modify the filename of a log file when rotating.

-This is provided so that a custom filename can be provided.

-The default implementation calls the 'namer' attribute of the
-handler, if it's callable, passing the default name to
-it. If the attribute isn't callable (the default is None), the name
-is returned unchanged.

-:param default_name: The default name for the log file.
- -
-Methods inherited from logging.FileHandler:
-
__repr__(self)
Return repr(self).
- -
close(self)
Closes the stream.
- -
-Methods inherited from logging.StreamHandler:
-
flush(self)
Flushes the stream.
- -
setStream(self, stream)
Sets the StreamHandler's stream to the specified value,
-if it is different.

-Returns the old stream, if the stream was changed, or None
-if it wasn't.
- -
-Data and other attributes inherited from logging.StreamHandler:
-
terminator = '\n'
- -
-Methods inherited from logging.Handler:
-
acquire(self)
Acquire the I/O thread lock.
- -
createLock(self)
Acquire a thread lock for serializing access to the underlying I/O.
- -
format(self, record)
Format the specified record.

-If a formatter is set, use it. Otherwise, use the default formatter
-for the module.
- -
get_name(self)
- -
handle(self, record)
Conditionally emit the specified logging record.

-Emission depends on filters which may have been added to the handler.
-Wrap the actual emission of the record with acquisition/release of
-the I/O thread lock. Returns whether the filter passed the record for
-emission.
- -
handleError(self, record)
Handle errors which occur during an emit() call.

-This method should be called from handlers when an exception is
-encountered during an emit() call. If raiseExceptions is false,
-exceptions get silently ignored. This is what is mostly wanted
-for a logging system - most users will not care about errors in
-the logging system, they are more interested in application errors.
-You could, however, replace this with a custom handler if you wish.
-The record which was being processed is passed in to this method.
- -
release(self)
Release the I/O thread lock.
- -
setFormatter(self, fmt)
Set the formatter for this handler.
- -
setLevel(self, level)
Set the logging level of this handler.  level must be an int or a str.
- -
set_name(self, name)
- -
-Data descriptors inherited from logging.Handler:
-
name
-
-
-Methods inherited from logging.Filterer:
-
addFilter(self, filter)
Add the specified filter to this handler.
- -
filter(self, record)
Determine if a record is loggable by consulting all the filters.

-The default is to allow the record to be logged; any filter can veto
-this and the record is then dropped. Returns a zero value if a record
-is to be dropped, else non-zero.

-.. versionchanged:: 3.2

-   Allow filters to be just callables.
- -
removeFilter(self, filter)
Remove the specified filter from this handler.
- -
-Data descriptors inherited from logging.Filterer:
-
__dict__
-
dictionary for instance variables (if defined)
-
-
__weakref__
-
list of weak references to the object (if defined)
-
-

- - - - - - - -
 
-class DefaultNamespace(types.SimpleNamespace)
   DefaultNamespace(required: List[str] = [], optional: List[str] = [], default: dict = {}, types: dict = {})

-This class build simple namespace with default
-attributs.
 
 
Method resolution order:
-
DefaultNamespace
-
types.SimpleNamespace
-
builtins.object
-
-
-Methods defined here:
-
__getitem__(self, key: str)
Compatibility with dict.
- -
__init__(self, required: List[str] = [], optional: List[str] = [], default: dict = {}, types: dict = {})
Initialize self.  See help(type(self)) for accurate signature.
- -
build_type(self, attribut: str, value: Any, type_: type = None) -> None
This function builds type from configuration value.
- -
build_types(self) -> None
This function builds type from configuration values.
- -
check_required(self) -> None
This function checks required attributes
-if one of required attributes is missing this
-function raise MissingAttributesError.
- -
export_as_json(self, name: str = None) -> None
This function export namespace values (useful for debugging).
- -
get(self, key: str, default=None)
Compatibility with dict.
- -
get_dict(self) -> None
This function return a dict of attributes.
- -
get_missings(self) -> List[str]
This function checks required attributes
-and return a List[str] of missing required attributes.
- -
get_unexpecteds(self, log: bool = True) -> List[str]
This function return a List[str] of
-all attributes not in optional and
-required attributes.

-If log argument is True a Warning log message is
-write for all unexpected attributes.
- -
set_defaults(self) -> None
This function set defaults attribut with defaults values.
- -
update(self, **kwargs)
This function add/update attributes with **kwargs arguments.
- -
-Class methods defined here:
-
default_build(**kwargs) -> ~DefaultNamespace from builtins.type
Default build for DefaultNamespace (set defaults, add values,
-check requirements and unexpected values and build types).
- -
-Data descriptors defined here:
-
__weakref__
-
list of weak references to the object (if defined)
-
-
-Methods inherited from types.SimpleNamespace:
-
__delattr__(self, name, /)
Implement delattr(self, name).
- -
__eq__(self, value, /)
Return self==value.
- -
__ge__(self, value, /)
Return self>=value.
- -
__getattribute__(self, name, /)
Return getattr(self, name).
- -
__gt__(self, value, /)
Return self>value.
- -
__le__(self, value, /)
Return self<=value.
- -
__lt__(self, value, /)
Return self<value.
- -
__ne__(self, value, /)
Return self!=value.
- -
__reduce__(...)
Return state information for pickling
- -
__repr__(self, /)
Return repr(self).
- -
__setattr__(self, name, value, /)
Implement setattr(self, name, value).
- -
-Static methods inherited from types.SimpleNamespace:
-
__new__(*args, **kwargs) from builtins.type
Create and return a new object.  See help(type) for accurate signature.
- -
-Data descriptors inherited from types.SimpleNamespace:
-
__dict__
-
-
-Data and other attributes inherited from types.SimpleNamespace:
-
__hash__ = None
- -

- - - - - - - -
 
-Logs = class _Logs(builtins.object)
   This class implements basic python logs.
 
 Methods defined here:
-
access(log: str) -> None
This function implements access logs for WebScripts.
- -
authentication(log: str) -> None
This function implements authentication logs for WebScripts.
- -
command(log: str) -> None
This function implements response logs for WebScripts.
- -
config(*args, **kwargs)
This function config ROOT logger.
- -
critical(log: str) -> None
This function implements basic python critical logs for WebScripts.
- -
debug(log: str) -> None
This function implements basic python debug logs for WebScripts.
- -
error(log: str) -> None
This function implements basic python error logs for WebScripts.
- -
exception(log: str) -> None
This function implements basic python exception (error) logs for
-WebScripts.
- -
info(log: str) -> None
This function implements basic python info logs for WebScripts.
- -
response(log: str) -> None
This function implements response logs for WebScripts.
- -
trace(log: str) -> None
This function implements trace logs for WebScripts.
- -
warning(log: str) -> None
This function implements basic python warning logs for WebScripts.
- -
-Data descriptors defined here:
-
__dict__
-
dictionary for instance variables (if defined)
-
-
__weakref__
-
list of weak references to the object (if defined)
-
-
-Data and other attributes defined here:
-
__annotations__ = {'console': <class 'logging.Logger'>, 'file': <class 'logging.Logger'>, 'log_access': <class 'logging.Logger'>, 'log_authentication': <class 'logging.Logger'>, 'log_command': <class 'logging.Logger'>, 'log_critical': <class 'logging.Logger'>, 'log_debug': <class 'logging.Logger'>, 'log_error': <class 'logging.Logger'>, 'log_info': <class 'logging.Logger'>, 'log_response': <class 'logging.Logger'>, ...}
- -
console = <Logger WebScripts.console (WARNING)>
- -
file = <Logger WebScripts.file (WARNING)>
- -
log_access = <Logger WebScripts.access (WARNING)>
- -
log_authentication = <Logger WebScripts.authentication (WARNING)>
- -
log_command = <Logger WebScripts.command (WARNING)>
- -
log_critical = <Logger WebScripts.critical (WARNING)>
- -
log_debug = <Logger WebScripts.debug (WARNING)>
- -
log_error = <Logger WebScripts.error (WARNING)>
- -
log_info = <Logger WebScripts.info (WARNING)>
- -
log_response = <Logger WebScripts.response (WARNING)>
- -
log_trace = <Logger WebScripts.trace (WARNING)>
- -
log_warning = <Logger WebScripts.warning (WARNING)>
- -

- - - - - -
 
-Functions
       
check_file_permission(configuration: utils.DefaultNamespace, filepath: str, recursive: bool = False, executable: bool = False, dir_check: bool = True) -> bool
This function checks files and directories permissions for security.
-
get_arguments_count(object_: collections.abc.Callable)
This function return the number of argument to call this Callable object.
-
get_encodings()
This function returns the probable encodings.
-
get_file_content(file_path, *args, as_iterator: bool = False, **kwargs) -> ~StrOrBytes
This function return the file content.
-
get_ini_dict(filename: str) -> Dict[str, Dict[str, str]]
This function return a dict from ini filename.
-
get_ip(environ: os._Environ, ip_number: int = None, protected: bool = True) -> str
This function return the real IP.
-
get_real_path(file_path: str, is_dir: bool = False, no_error: bool = False) -> str
This function return the real path for files.
-
log_trace(function: Union[function, method]) -> Union[function, method]
This decorator traces functions (start and end).
-
logger_access = access(log: str) -> None
This function implements access logs for WebScripts.
-
logger_auth = authentication(log: str) -> None
This function implements authentication logs for WebScripts.
-
logger_command = command(log: str) -> None
This function implements response logs for WebScripts.
-
logger_critical = critical(log: str) -> None
This function implements basic python critical logs for WebScripts.
-
logger_debug = debug(log: str) -> None
This function implements basic python debug logs for WebScripts.
-
logger_error = error(log: str) -> None
This function implements basic python error logs for WebScripts.
-
logger_info = info(log: str) -> None
This function implements basic python info logs for WebScripts.
-
logger_response = response(log: str) -> None
This function implements response logs for WebScripts.
-
logger_warning = warning(log: str) -> None
This function implements basic python warning logs for WebScripts.
-

- - - - - -
 
-Data
       __all__ = ['Logs', 'DefaultNamespace', 'log_trace', 'get_ip', 'get_arguments_count', 'get_file_content', 'get_real_path', 'get_encodings', 'get_ini_dict', 'server_path', 'CustomLogHandler', 'logger_debug', 'logger_info', 'logger_auth', 'logger_access', 'logger_response', 'logger_command', 'logger_warning', 'logger_error', 'logger_critical', ...]
-__annotations__ = {'date_format': <class 'str'>, 'logger_access': <class 'collections.abc.Callable'>, 'logger_auth': <class 'collections.abc.Callable'>, 'logger_command': <class 'collections.abc.Callable'>, 'logger_critical': <class 'collections.abc.Callable'>, 'logger_debug': <class 'collections.abc.Callable'>, 'logger_error': <class 'collections.abc.Callable'>, 'logger_info': <class 'collections.abc.Callable'>, 'logger_response': <class 'collections.abc.Callable'>, 'logger_trace': <class 'collections.abc.Callable'>, ...}
-__author_email__ = 'mauricelambert434@gmail.com'
-__copyright__ = '\nWebScripts Copyright (C) 2021, 2022, 2023 Mau...ome to redistribute it\nunder certain conditions.\n'
-__description__ = '\nThis tool run scripts and display the result in...cripts (Logs, Namespace for configuration, ...).\n'
-__license__ = 'GPL-3.0 License'
-__maintainer__ = 'Maurice Lambert'
-__maintainer_email__ = 'mauricelambert434@gmail.com'
-__url__ = 'https://github.com/mauricelambert/WebScripts'
-server_path = /home/WebScripts

- - - - - -
 
-Author
       Maurice Lambert
- \ No newline at end of file diff --git a/static/images/WebScripts1.png b/static/images/WebScripts1.png deleted file mode 100644 index d2fcf29c..00000000 Binary files a/static/images/WebScripts1.png and /dev/null differ diff --git a/static/images/WebScripts2.png b/static/images/WebScripts2.png deleted file mode 100644 index 6e274d19..00000000 Binary files a/static/images/WebScripts2.png and /dev/null differ diff --git a/static/images/WebScripts3.png b/static/images/WebScripts3.png deleted file mode 100644 index 7befd39d..00000000 Binary files a/static/images/WebScripts3.png and /dev/null differ diff --git a/static/images/WebScripts4.png b/static/images/WebScripts4.png deleted file mode 100644 index a8bd0507..00000000 Binary files a/static/images/WebScripts4.png and /dev/null differ diff --git a/static/images/WebScripts5.png b/static/images/WebScripts5.png deleted file mode 100644 index 2ac4297b..00000000 Binary files a/static/images/WebScripts5.png and /dev/null differ diff --git a/static/images/WebScripts6.png b/static/images/WebScripts6.png deleted file mode 100644 index d8f4fb45..00000000 Binary files a/static/images/WebScripts6.png and /dev/null differ diff --git a/static/images/WebScripts7.png b/static/images/WebScripts7.png deleted file mode 100644 index 99c66c78..00000000 Binary files a/static/images/WebScripts7.png and /dev/null differ diff --git a/static/images/webscripts_header.jpg b/static/images/webscripts_header.jpg deleted file mode 100644 index 8a5453f9..00000000 Binary files a/static/images/webscripts_header.jpg and /dev/null differ diff --git a/static/images/webscripts_header.png b/static/images/webscripts_header.png deleted file mode 100644 index 2ac4297b..00000000 Binary files a/static/images/webscripts_header.png and /dev/null differ diff --git a/static/images/webscripts_icon.jpg b/static/images/webscripts_icon.jpg deleted file mode 100644 index 8a5453f9..00000000 Binary files a/static/images/webscripts_icon.jpg and /dev/null differ diff --git a/static/images/webscripts_icon.png b/static/images/webscripts_icon.png deleted file mode 100644 index d8f4fb45..00000000 Binary files a/static/images/webscripts_icon.png and /dev/null differ diff --git a/static/js/webscripts_index_js_scripts.js b/static/js/webscripts_index_js_scripts.js deleted file mode 100644 index dd1477b0..00000000 --- a/static/js/webscripts_index_js_scripts.js +++ /dev/null @@ -1,21 +0,0 @@ -/* - - Scripts for index pages. - Copyright (C) 2021 Maurice Lambert - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - -*/ - -is_index = true; diff --git a/static/js/webscripts_js_scripts.js b/static/js/webscripts_js_scripts.js deleted file mode 100644 index 11da5de4..00000000 --- a/static/js/webscripts_js_scripts.js +++ /dev/null @@ -1,583 +0,0 @@ -/* - - Basic web scripts for WebScript pages. - Copyright (C) 2021, 2022 Maurice Lambert - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - -*/ - -let scripts; -let subpath = "../"; -let is_index = false; -let url = new URL(window.location); -let dark_theme = window.matchMedia("(prefers-color-scheme: dark)").matches; - -/* -This class implements a script object. -*/ -class Script { - constructor(script) { - let name = this.name = script.name; - this.name_lower = name.toLowerCase(); - - let category = this.category = script.category; - this.category_lower = category.toLowerCase(); - - let description = this.description = script.description; - this.description_lower = description.toLowerCase(); - - this.arguments = script.args; - - this.scripts[name] = this; - } - - /* - This function builds the script "cards" for the index page and the - search bar results. - */ - build_card(attribut, ...classes) { - let doc_createElement = document.createElement.bind(document); - let link_script_card = this[attribut] = doc_createElement("a"); - let div_script_card = doc_createElement("div"); - let classList = div_script_card.classList; - let div_class_list_add = classList.add.bind(classList); - let append_child = div_script_card.appendChild.bind( - div_script_card); - let name = this.name; - - if (name === "/auth/") { - link_script_card.href = subpath + "web/auth/"; - } else { - link_script_card.href = subpath + "web/scripts/" + name; - } - - link_script_card.style.textDecoration = 'none'; - - for (let class_ of classes) { - div_class_list_add(class_); - } - - let title = doc_createElement("h4"); - title.innerText = name + " (" + this.category + ")"; - - let description = doc_createElement("p"); - description.innerText = this.description; - description.classList.add("search_result_description"); - - append_child(title); - append_child(description); - link_script_card.appendChild(div_script_card); - } - - /* - This function implements the search method to research a script, - on a script page or/and the index page. - */ - search() { - let is_light = document.getElementsByClassName('light').length; - let getElementById = document.getElementById.bind(document); - - let path = url.pathname; - let path_split = path.split('/web/'); - let path_search = path_split.length === 2 && ! - path_split[1].includes("/") && path_split[1]; - let search_value = getElementById("webscripts_search_bar").value; - let pattern = search_value || path_search || ""; - pattern = pattern.toLowerCase(); - - let search_result_container = getElementById("search_result"); - let result_container_appendChild = search_result_container - .appendChild.bind(search_result_container); - - let container = getElementById("webscripts_content"); - let container_appendChild = container.appendChild.bind(container); - - let used_categories = new Set(); - let used_categories_add = used_categories.add.bind(used_categories); - let used_categories_has = used_categories.has.bind(used_categories); - let categories = Script.prototype.categories; - - if (is_index) { - container.innerHTML = ""; - } - - search_result_container.innerHTML = ""; - - if (search_value) { - search_result_container.style.display = 'initial'; - - } else { - search_result_container.style.display = 'none'; - } - - let no_result = true; - - for (let script of Object.values(Script.prototype.scripts)) { - let category_name = script.category; - if (script.name_lower.includes(pattern) || script.category_lower - .includes(pattern) || script.description_lower.includes( - pattern)) { - if (is_index && category_name) { - no_result = false; - let category = categories[category_name]; - category.appendChild(script.link_card); - - if (!used_categories_has(category_name)) { - used_categories_add(category_name); - container_appendChild(category); - } - } - - if (search_value) { - let button = script.search_button; - if ( - is_light && !button.classList.contains("light") || - !is_light && button.classList.contains("light") - ) { - let div = button.firstChild; - button.classList.toggle("light"); - div.classList.toggle("light"); - div.getElementsByClassName( - "search_result_description" - )[0].classList.toggle("light"); - } - result_container_appendChild(script.search_button); - } - } - } - - if (path_search && no_result) { - container.innerHTML = - "

There is no script matching " + - "your search.

"; - } - } - - /* - This function builds the script category if not exists. - */ - build_category() { - let category = this.category; - let categories = this.categories; - if (category && !categories.hasOwnProperty(category)) { - let doc_createElement = document.createElement.bind(document); - let div_category = categories[category] = doc_createElement( - "div"); - let dic_classList = div_category.classList; - let div_class_list_add = dic_classList.add.bind(dic_classList); - - div_class_list_add("category"); - // div_class_list_add("category_content"); - - let title = doc_createElement("h3"); - let title_classList = title.classList; - let title_class_list_add = title_classList.add.bind( - title_classList); - title_class_list_add("category"); - title_class_list_add("category_title"); - } - } - - /* - This function is a "constructor" to build all scripts. - */ - build_scripts(scripts) { - for (let script_basic of Object.values(scripts)) { - let script = new Script(script_basic); - script.build_category(); - script.build_card("link_card", "script_cards", - "category_content"); // "category" - script.build_card("search_button", "search_result"); - } - - Script.prototype.search(); - } -} - -Script.prototype.categories = {}; -Script.prototype.scripts = {}; - -function get_scripts(...functions) { - let xhttp = new XMLHttpRequest(); - xhttp.onreadystatechange = () => { - if (xhttp.readyState === 4 && xhttp.status === 200) { - scripts = JSON.parse(xhttp.responseText); - Script.prototype.build_scripts(scripts); - - for (let func of functions) { - if (func) { - func(); - } - } - } - }; - - xhttp.open("GET", subpath + "api/", true); - xhttp.send(); -} - -/* -This class implements theme functions. -*/ -class Theme { - - constructor() { - this.button = document.getElementById("webscripts_theme_button"); - } - - /* - This function gets elements from the web page and changes the theme of each. - */ - change_elements(class_name = 'light', element = null) { - let elements = null; - - if (element === null) { - let getElementsByTagName = document.getElementsByTagName.bind( - document); - let getElementByClass = document.getElementsByClassName.bind( - document); - let getElementById = document.getElementById.bind(document); - - elements = [ - getElementsByTagName('html')[0], - document.body, - getElementById("webscripts_content"), - getElementById("webscripts_menu"), - ...getElementByClass('border'), - ...getElementByClass('category'), - ...getElementByClass('search_result'), - ...getElementByClass('category_content'), - ...getElementByClass('webscripts_column_select'), - ...getElementByClass('search_result_description'), - ...getElementsByTagName('button'), - ...getElementsByTagName('input'), - ...getElementsByTagName('select'), - ...getElementsByTagName('a'), - ...getElementsByTagName('td'), - ...getElementsByTagName('option'), - ...getElementsByTagName('progress'), - ]; - - let bar = getElementById("bar"); - if (bar !== null) { - elements.push(bar); - } - } else { - let getElementsByTagName = element.getElementsByTagName.bind( - element); - - elements = [ - ...element.getElementsByClassName( - 'webscripts_column_select'), - ...element.getElementsByClassName('border'), - ...getElementsByTagName('button'), - ...getElementsByTagName('input'), - ...getElementsByTagName('select'), - ...getElementsByTagName('a'), - ...getElementsByTagName('td'), - ]; - } - - for (let temp_element of elements) { - temp_element.classList.toggle(class_name); - } - } - - /* - This function reverses the theme of the Web page. - */ - reverse() { - let theme = localStorage.getItem('theme'); - - if (theme === "light") { - dark_theme = true; - localStorage.setItem('theme', 'dark'); - this.button.innerText = "Light theme"; - } else if (theme === "dark") { - dark_theme = false; - localStorage.setItem('theme', 'light'); - this.button.innerText = "Dark theme"; - } - - Theme.prototype.change_elements(); - } - - /* - This function changes the theme when the page loads. - */ - load() { - if ((localStorage.getItem('theme') === null && dark_theme) || - localStorage.getItem('theme') === "dark") { - localStorage.setItem('theme', 'dark'); - } else if (localStorage.getItem('theme') === "light" || localStorage - .getItem('theme') === null) { - localStorage.setItem('theme', 'light'); - Theme.prototype.change_elements(); - this.button.innerText = "Dark theme"; - } else { - localStorage.setItem('theme', 'dark'); - } - } -} - -/* -This class builds the header sticker. -*/ -class HeaderSticker { - constructor() { - let canvas = this.canvas = document.createElement("canvas"); - canvas.id = "webscripts_header_canvas_image"; - - let height = this.height = document.getElementById( - 'webscripts_header_text_position').offsetHeight; - canvas.style.height = height + "px"; - - this.context = canvas.getContext("2d"); - - let image = this.image = new Image(); - image.onload = this.add.bind(this); - image.src = subpath + 'static/webscripts_icon.png'; - } - - /* - This method adds the sticker to the header of the web page. - */ - add() { - let context = this.context; - let image = this.image; - let height = this.height; - let divise = image.height / height; - let width = Math.round(image.width / divise); - let canvas = this.canvas; - - let start_x = Math.round((canvas.width - 200) / 2); - let start_y = Math.round((canvas.height - 160) / 2); - - context.drawImage(image, start_x, start_y, 200, 160); - let sticker = this.effect(20); - context.drawImage(sticker, start_x, start_y, 200, 160); - - let container = document.getElementById( - 'webscripts_header_canvas_container'); - container.style.height = height + "px"; - container.appendChild(this.canvas); - } - - /* - This method builds the sticker effect. - */ - effect(grow) { - let canvas1 = document.createElement("canvas"); - let context1 = canvas1.getContext("2d"); - let canvas2 = document.createElement("canvas"); - let context2 = canvas2.getContext("2d"); - let image = this.image; - - canvas1.width = canvas2.width = image.width + grow * 2; - canvas1.height = canvas2.height = image.height + grow * 2; - context1.drawImage(image, grow, grow); - context2.shadowColor = 'white'; - context2.shadowBlur = 2; - - for (let i = 0; i < grow; i++) { - context2.drawImage(canvas1, 0, 0); - context1.drawImage(canvas2, 0, 0); - } - - context2.shadowColor = 'rgba(0,0,0,0)'; - context2.drawImage(image, grow, grow); - - this.sticker = canvas2; - return canvas2; - } -} - -/* -This class implements the button menu actions. -*/ -class Menu { - - constructor() { - this.container = document.getElementById("webscripts_menu_values"); - } - - /* - This function cleans the console. - */ - clear() { - document.getElementById("script_outputs").innerText = ""; - download_text = ""; - } - - /* - This function go back ton index page. - */ - index() { - window.location = new URL(subpath + "web/", window.location); - } - - /* - This function downloads the console content. - */ - download() { - let body = document.body; - let download_link = document.createElement('a'); - let download_link_set = download_link.setAttribute.bind( - download_link); - - download_link_set('href', - `data:text/${download_type};charset=utf-8,` + - encodeURIComponent( - download_text)); - download_link_set('download', `result_${script_name}` + - download_extension); - - body.appendChild(download_link); - download_link.click(); - body.removeChild(download_link); - } - - /* - This function shows or hides the buttons. - */ - change_display() { - let display = this.container.style.display; - - if (!display || display == "none") { - this.container.style.display = "inline-block"; - } else { - this.container.style.display = "none"; - } - } - - /* - This function build an URL to relaunch this script execution. - */ - get_execution_url() { - let getElementsByTagName = document.getElementsByTagName.bind( - document); - let elements = Array.from(getElementsByTagName('input')).concat( - Array.from(getElementsByTagName('select'))); - - url.search = ""; - let append = url.searchParams.append.bind(url.searchParams) - - for (let element of elements) { - if (element.name && element.value && element.type !== - "password" && element.type !== "file" && element.type !== - "submit" && element.type !== "button" && element.name !== - "csrf_token") { - append(element.name, element.value); - } - } - - navigator.clipboard.writeText(url.href); - this.toast("Copied"); - } - - /* - This function copy the full output. - */ - copy_output() { - navigator.clipboard.writeText(download_text); - this.toast("Copied"); - } - - /* - This function creates a notify toast. - */ - toast(text) { - let toast = document.createElement('div'); - toast.classList.add("toast", "show"); - toast.innerText = text; - document.body.appendChild(toast); - setTimeout(() => { - document.body.removeChild(toast); - }, 3000) - } -} - -/* -This class implements notification system. -*/ -class Notification { - /* - This function closes a notification. - */ - close() { - this.style.display = "none"; - let notifications = JSON.parse(localStorage.getItem( - 'notifications_closed')); - - if (notifications) { - notifications.push(this.id); - } else { - notifications = [this.id]; - } - - localStorage.setItem('notifications_closed', JSON.stringify( - notifications)); - } -} - -/* -This function is performed when the Web page is loaded. -*/ -window.onload = (first, script_onload = null, ...functions) => { - let getById = document.getElementById.bind(document); - - let theme = new Theme(); - get_scripts(script_onload, theme.load.bind(theme)); - - let header_sticker = new HeaderSticker(); - - getById("webscripts_search_bar").onkeyup = Script.prototype.search; - - let menu = new Menu(); - getById("webscripts_menu_button_left").onclick = menu.change_display - .bind(menu); - getById("webscripts_theme_button").onclick = theme.reverse.bind(theme); - - for (let func of functions) { - func(); - } - - let notifications = new Set(JSON.parse(localStorage.getItem( - 'notifications_closed'))); - - if (notifications) { - for (let notification of notifications) { - let div = getById(notification); - - if (div) { - div.style.display = "none"; - } else { - notifications.delete(notification); - } - - } - } - - localStorage.setItem('notifications_closed', JSON.stringify([... - notifications - ])); - - notifications = document.getElementsByClassName('notification_close'); - - for (let notification of notifications) { - notification.onclick = Notification.prototype.close.bind( - notification.parentNode); - } -} \ No newline at end of file diff --git a/static/js/webscripts_script_js_scripts.js b/static/js/webscripts_script_js_scripts.js deleted file mode 100644 index 50a163d5..00000000 --- a/static/js/webscripts_script_js_scripts.js +++ /dev/null @@ -1,1500 +0,0 @@ -/* - - Scripts for script.html - Copyright (C) 2021, 2022, 2024 Maurice Lambert - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - -*/ - -let script_name; -let script; -let download_extension = ".txt"; -let download_text = ""; -let download_type = "plain"; -let download_separator = "\n"; -let execution_number = 0; - -/* -This class implements an argument. -*/ -class Argument { - constructor(argument) { - let container = this.container = document.createElement("div"); - this.javascript_attributs = argument.javascript_attributs; - this.predefined_values = argument.predefined_values; - this.label = document.createElement("label"); - this.default_value = argument.default_value; - this.description = argument.description; - this.is_advanced = argument.is_advanced; - this.html_type = argument.html_type; - this.example = argument.example; - this.name = argument.name; - this.list = argument.list; - - container.classList.add("row"); - } - - /* - This method groups arguments elements. - */ - group_argument_elements(...dom_elements) { - let container = this.container; - let appendChild = container.appendChild.bind(container); - - for (let element of dom_elements) { - appendChild(element); - } - - return container; - } - - /* - This method adds the description if exists. - */ - add_description() { - let description = this.description; - - if (description === undefined && description === null) { - return; - } - - let paragraph = document.createElement("p"); - let classList = paragraph.classList; - let add = classList.add.bind(classList); - - add("inline"); - add("description"); - add("script_presentation"); - - paragraph.innerText = description; - - return paragraph; - } - - /* - This method adds the label (argument name). - */ - add_label() { - let name = this.name; - let label = this.label; - let classList = label.classList; - let add_class = classList.add.bind(classList); - - label.htmlFor = name; - label.innerText = name + " :"; - add_class("inline"); - add_class("script_presentation"); - - return label; - } - - /* - This method generates argument values - when this argument has predefined values. - */ - build_select() { - let select = this.dom_value = document.createElement("select"); - let default_value = this.default_value; - let name = this.name; - let option; - - let appendChild = select.appendChild.bind(select); - select.name = name; - select.id = name; - - if (this.list) { - select.multiple = true; - } - - if (default_value !== undefined && default_value !== null) { - select.value = default_value; - } - - for (let value of this.predefined_values) { - option = document.createElement("option"); - option.value = option.innerText = value; - - appendChild(option); - } - - return select; - } - - /* - This method builds the argument input. - */ - build_input() { - let input = this.dom_value = document.createElement("input"); - let default_value = this.default_value; - let example = this.example; - let name = this.name; - - input.id = name; - input.name = name; - input.type = this.html_type; - - input.value = default_value || ""; - input.placeholder = example || ""; - - if (this.list) { - input.id = name + document.getElementsByName(name).length; - input.onkeyup = this.onchange_input_list.bind(input); - } - - return input; - } - - /* - This method implements the onchange event to have multiple values for - an argument. - */ - onchange_input_list(event) { - let source = event.target || event.srcElement; - let name = source.name; - - let elements = document.getElementsByName(name); - let one_empty = false; - - for (let element of elements) { - if (element.value === "" && one_empty) { - element.remove() - } else if (element.value === "") { - one_empty = true; - } - } - - if (!one_empty) { - let id_num = elements.length; - let id = name + id_num; - let element = document.getElementById(id); - - while (element) { - id_num++; - id = name + id_num; - element = document.getElementById(id); - } - - let new_element = source.cloneNode(); - new_element.id = id; - source.parentNode.appendChild(new_element); - new_element.onkeyup = this.onkeyup.bind(new_element); - new_element.value = ""; - } - } -} - -/* -This class implements the script interface. -*/ -class ScriptInterface { - constructor(name) { - this.name = name; - this.script = Script.prototype.scripts[name]; - - let getElementById = document.getElementById.bind(document); - this.advanced_button = getElementById("print_advanced"); - this.description_container = getElementById("script_description"); - this.advanced_arguments_container = getElementById( - "advanced_container"); - let script_container = this.script_container = getElementById( - "script_interface"); - let advanced_arguments = this.advanced_arguments = getElementById( - "advanced_arguments"); - - this.advanced_arguments_add = advanced_arguments.appendChild.bind( - advanced_arguments); - this.insert_argument = script_container.insertBefore.bind( - script_container); - } - - /* - This method adds the script description on the web page. - */ - add_description() { - this.description_container.innerText = this.script.description; - } - - /* - This method sets HTML/CSS custom attributes. - */ - set_custom_attributes(dom_element, attributes) { - for (let [attribute, value] of Object.entries(attributes)) { - dom_element.setAttribute(attribute, value); - } - } - - /* - This method hides the advanced arguments container - or defined the button behavior. - */ - config_advanced_arguments() { - let advanced_arguments_container = this - .advanced_arguments_container; - if (!advanced_arguments_container.getElementsByClassName("row") - .length) { - advanced_arguments_container.style.display = "none"; - } else { - let arguments_ = this.advanced_arguments; - let button = this.advanced_button; - button.onclick = () => { - if (arguments_.style.display && - arguments_.style.display !== "none") { - arguments_.style.display = "none"; - button.innerText = "Show advanced arguments"; - } else { - arguments_.style.display = "block"; - button.innerText = "Hide advanced arguments"; - } - }; - } - } - - /* - This method sets values stocked in URL query string. - */ - set_url_values() { - let event; - let element, element_id; - let query = location.search; - let counters = {}; - - query = query.substr(1); - query.split("&").forEach(function(part) { - let item = part.split("="); - element_id = element = decodeURIComponent(item[0].replaceAll("+", - " ")); - counters[element] = counters[element] ? counters[element] : 0; - - if (element) { - element = document.getElementById(element) || document.getElementById(element + counters[element]); - counters[element_id] += 1; - - if (element) { - if (element.type === "checkbox" && item[1] === - "on") { - element.checked = true; - } else { - element.value = decodeURIComponent(item[1] - .replaceAll("+", " ")); - event = new Event('change'); - element.dispatchEvent(event); - } - - if (element.onkeyup) { - element.onkeyup({'target': element}) - } - } - } - }); - } - - /* - This method adds script arguments in web interface. - */ - add_arguments() { - for (let argument of this.script.arguments) { - let dom_argument; - argument = new Argument(argument); - let container = document.createElement("div"); - let classList = container.classList; - let addClass = classList.add.bind(classList); - - addClass("argument_container"); - addClass("inline"); - - dom_argument = (argument.predefined_values !== undefined && - argument.predefined_values !== null) ? argument - .build_select() : argument.build_input(); - - dom_argument.addEventListener('keypress', (event) => { - if (event.keyCode === 13) { - ScriptExecution.prototype.start(); - } - }); - - this.set_custom_attributes(dom_argument, argument - .javascript_attributs); - - container.appendChild(dom_argument); - - let label = argument.add_label(dom_argument); - let paragraph = argument.add_description(); - let elements_container = argument.group_argument_elements(label, - paragraph, container); - - argument.is_advanced ? this.advanced_arguments_add( - elements_container) : this.insert_argument( - elements_container, this.advanced_arguments_container); - } - } -} - -/* -Add a function to run when the Web page is loaded. -*/ -let onload2 = window.onload; -window.onload = (first, script_onload = null, ...functions) => { - onload2(window, script_onload = () => { - script = new ScriptInterface(script_name); - - script.add_description(); - script.add_arguments(); - - script.set_url_values(); - script.config_advanced_arguments(); - - document.getElementById("submit_button").onclick = - ScriptExecution.prototype.start; - - let menu = new Menu(); - let history = new History(); - document.getElementById("webscripts_copy_execution_button") - .onclick = menu.get_execution_url.bind(menu); - document.getElementById("webscripts_menu_button_right") - .onclick = history.change_display.bind(history); - document.getElementById("webscripts_copy_output_button") - .onclick = menu.copy_output.bind(menu); - document.getElementById("webscripts_download_button") - .onclick = menu.download.bind(menu); - document.getElementById("webscripts_index_button").onclick = - menu.index.bind(menu); - document.getElementById("webscripts_clear_button").onclick = - menu.clear.bind(menu); - }); -}; - -/* -This class implements object and methods to run scripts. -*/ -class ScriptExecution { - constructor() { - let getElementsByTagName = document.getElementsByTagName.bind( - document); - let getElementById = document.getElementById.bind(document); - this.dom_arguments = Array.from(getElementsByTagName('input')) - .concat( - Array.from(getElementsByTagName('select'))); - this.script_container = getElementById("script_interface"); - this.start_button = getElementById("submit_button"); - this.progressbar = getElementById("progressbar"); - this.error_container = getElementById("error"); - this.csrf = getElementById("csrf_token"); - this.progress_position = 0; - this.first_ouput = false; - this.is_running = false; - this.full_output = ""; - this.progress = true; - this.full_error = ""; - this.arguments = {}; - this.start = null; - this.time = null; - this.counter = 0; - this.end = null; - } - - /* - This method stringify the request body. - */ - send_json_request() { - this.send_requests({ - "csrf_token": this.csrf.value, - "arguments": this.arguments, - }); - } - - /* - This method gets a new DOM element and calls the - argument handler to get the value or prepares the - body and sends the request. - */ - get_argument_value() { - let argument = this.dom_arguments.pop(); - - if (argument) { - this.counter++; - this[`get_${argument.tagName}_value`](argument); - } else { - this.sort_arguments(); - this.send_json_request(); - this.start_button.disabled = true; - return; - } - } - - /* - Method handler for null arguments. - */ - get_NULL_value() {} - - /* - This method sorts arguments to send a valid request. - */ - sort_arguments() { - let sort = []; - - for (let [name, argument] of Object.entries(this.arguments)) { - sort.push([name, argument, argument["position"]]); - } - - sort.sort(function(a, b) { - return a[2] - b[2]; - }); - - let arguments_ = this.arguments = {}; - - for (let [name, argument, position] of sort) { - arguments_[name] = { - "value": argument["value"], - "input": argument["input"] - }; - } - } - - /* - This method is the onclick function for - start button (create a new instance - of ScriptExecution and use it). - */ - start(event) { - let script_exec = new ScriptExecution(); - script_exec.get_argument_value(); - } - - /* - This method animates the progress bar. - */ - progress_animation() { - let test, operation; - - if (progressbar.value >= 100) { - test = (value) => { - return value <= 0; - } - operation = (value) => { - return value - 1; - } - } else { - test = (value) => { - return value >= 100; - } - operation = (value) => { - return value + 1; - } - } - - if (this.progress) { - this.progress = false; - let interval = setInterval(() => { - let progressbar = this.progressbar; - if (test(progressbar.value)) { - clearInterval(interval); - this.progress = true; - - if (this.is_running) { - this.progress_animation(); - } - } else { - progressbar.value = operation(progressbar - .value); - } - }, 20); - } - } - - /* - This method gets input values. - */ - get_INPUT_value(input) { - if (input.type === "submit" || input.name === "csrf_token") { - this.get_argument_value(); - return; - } - - if (input.type === "checkbox") { - this.add_argument_value( - input.id, - input.name, - input.checked, - ); - } else if (input.type === "file") { - let reader = new FileReader(); - - reader.onload = (a) => { - this.add_argument_value( - input.id, - input.name, - window.btoa(a.target.result), - ); - }; - - if (input.files.length) { - reader.readAsBinaryString(input.files[0]); - } else { - this.get_argument_value(); - } - } else { - this.add_argument_value( - input.id, - input.name, - input.value, - ); - } - } - - /* - This method gets select values. - */ - get_SELECT_value(select) { - let dom_arguments = this.dom_arguments; - let selected = []; - let first = true; - - for (let option of select.options) { - if (option.selected) { - selected.push(option.value); - if (first) { - first = false; - } else { - dom_arguments.push({ - "tagName": "NULL" - }); - } - } - } - - if (selected.length) { - selected.forEach((item) => { - this.add_argument_value( - select.id, - select.name, - item, - ); - }); - } else { - this.add_argument_value( - select.id, - select.name, - "", - ); - } - - return dom_arguments; - } - - /* - This method adds an arguments value in the request data. - */ - add_argument_value(id, name, value) { - let arguments_ = this.arguments; - let argument = arguments_[name]; - - if (argument !== undefined) { - if (!Array.isArray(argument["value"])) { - argument["value"] = [argument["value"]]; - } - - if (value) { - argument["value"].push(value); - } - } else { - argument = arguments_[name] = { - "value": value, - "position": this.script_container.innerHTML.indexOf( - `id="${id}"`) - }; - - for (let argument_ of script.script.arguments) { - if (argument_.name !== name) { - continue; - } - - argument["input"] = (argument_.input === true) ? true : - false; - break; - } - } - - this.get_argument_value(); - } - - /* - This method returns 'light' when theme is light. - */ - get_theme() { - if (localStorage.getItem('theme') === "light" || window.matchMedia( - "(prefers-color-scheme: light)").matches) { - return 'light'; - } - } - - /* - This method redirects to referrer or to "/web/". - */ - redirect() { - let referrer = document.referrer; - window.location = (referrer && referrer.startsWith(window.location - .origin) && !referrer.endsWith("/web/auth/")) ? referrer : - window.location = new URL(subpath + "web/", window.location); - } - - /* - This method adds the error message when HTTP error is raised. - */ - http_error(status, message = null) { - this.error_container.innerText = "HTTP ERROR " + status; - - if (message) { - this.error_container.innerText += ": " + message - } - - this.error_container.innerText += ". \nYou can report a bug "; - - let class_link = this.get_theme(); - let link = document.createElement("a"); - - if (class_link) { - link.classList.add(class_link); - } - - link.href = subpath + "error_pages/Report/new/" + status; - link.innerText = "on the local report page"; - this.error_container.appendChild(link); - - this.script_end(); - } - - /* - This method sends the POST request to start script execution. - */ - send_requests(json, first = true) { - let xhttp; - this.xhttp = xhttp = new XMLHttpRequest(); - - xhttp.onreadystatechange = () => { - let status = xhttp.status; - - if (xhttp.readyState === 4) { - if (status === 200) { - this.response_manager(JSON.parse(xhttp - .responseText)); - } else if (status === 302 && script_name === "/auth/") { - this.redirect(); - } else if (status === 500) { - this.http_error( - status, - "\x49\x6e\x74\x65\x72\x6e\x61\x6c\x20" + - "\x53\x65\x72\x76\x65\x72\x20\x45\x72" + - "\x72\x6f\x72" - ); - } else if (status === 403) { - this.http_error(status, "Forbidden"); - } else { - this.http_error(status); - } - } - } - - let url = subpath + ( - script_name[0] === "/" ? script_name.slice(1) : "api/scripts/" + - script_name - ); - - xhttp.open("POST", url, true); - xhttp.setRequestHeader('Content-Type', 'application/json'); - xhttp.send(JSON.stringify(json)); - this.start = Date.now(); - - this.is_running = true; - this.progress_animation(); - } - - /* - This method requests the WebScripts server to - get a new line for "real time output". - */ - get_new_line(response) { - this.xhttp.open( - 'GET', subpath + `api/script/get/${response.key}`, true - ); - this.xhttp.send(); - } - - /* - This method resets the variables and calculates the time. - */ - script_end() { - let end = this.end = Date.now(); - this.start_button.disabled = false; - this.is_running = false; - this.full_output = ""; - this.full_error = ""; - - let diff_seconds = Math.round((end.valueOf() - this.start - .valueOf()) / - 1000); - let minutes = Math.round(diff_seconds / 60); - let seconds = diff_seconds - minutes * 60; - - if (minutes < 10) { - minutes = `0${minutes}`; - } - if (seconds < 10) { - seconds = `0${seconds}`; - } - - this.time = minutes + ":" + seconds; - this.first_ouput = true; - } - - /* - This method prints a new line and send - the new request to get a new line. - */ - real_time_handler(response) { - if (!response.code) { - response.code = "Running..."; - } - - if (!response.error) { - response.error = "Running..."; - } - - let output_builder = new OutputBuilder( - response, - false, - "Running...", - this.first_ouput, - false, - null, - null, - this.arguments, - ); - - if (output_builder.build()) { - this.first_ouput = false; - }; - this.get_new_line(response); - - return [response.stdout, response.stderr]; - } - - /* - This method detects the type of response and - uses the good behavior for this type of response. - */ - response_manager(response) { - let output, error; - - if (response.csrf) { - this.csrf.value = response.csrf; - } - - if (response.key) { - [output, error] = this.real_time_handler(response); - this.full_output += output; - this.full_error += error; - return; - } - - this.script_end(); - - let output_builder = new OutputBuilder( - response, - true, - this.time, - false, - true, - this.full_output, - this.full_error, - this.arguments, - ); - - output_builder.build(); - - document.getElementById('code').id = - `last_code_${execution_number}`; - document.getElementById('last_output').id = - `last_output_${execution_number}`; - document.getElementById('console').id = - `console_${execution_number}`; - } -} - -/* -This class implements functions to add a script execution in the web page. -*/ -class OutputBuilder { - constructor(output, add_history_ = true, time = null, make_new_output = - true, update = false, full_output = null, full_error = null, - user_inputs = {}) { - this.output = output; - this.add_history_ = add_history_; - this.time = time; - this.make_new_output = make_new_output; - this.update = update; - this.full_output = full_output; - this.full_error = full_error; - - this.console_div = document.getElementById("script_outputs"); - this.content_type = output["Content-Type"]; - this.stderr_content_type = "text/plain"; - this.text = ""; - this.html = ""; - this.code; - this.new_output; - - this.user_inputs = user_inputs; - - this.error_string = this.clean_string(output.stderr); - this.output_string = this.clean_string(output.stdout); - } - - /* - This function deletes whitespace characters on - the start and the end of the string. - */ - clean_string(string) { - return string.replace(/^\s+|\s+$/g, ''); - }; - - /* - This function creates the "code" element and - add the script execution status. - */ - code_builder(output, time) { - let code = document.createElement("code"); - code.id = "code"; - code.classList.add("code"); - - code.innerText = - `>>> ${script_name} ExitCode: ${output.code} ` + - `Error: ${output.error}`; - - if (time) { - code.innerText += ` ExecutionTime: ${time}`; - } - - return code; - } - - /* - This function creates containers - for text and HTML content. - */ - new(code) { - let new_output = document.createElement("div"); - let console_ = document.createElement("pre"); - - new_output.id = "last_output"; - - console_.id = "console"; - console_.classList.add("console"); - - console_.appendChild(code); - new_output.appendChild(console_); - - this.make_new_output = true; - - return new_output; - } - - /* - This function unescapes HTML special characters. - */ - unescape = str => str.replace(/</g, '<').replace(/>/g, '>') - .replace(/'/g, "'").replace(/"/g, '"').replace(/&/g, '&'); - - /* - This function escapes HTML special characters. - */ - escape = str => str.replace(/&/g, '&').replace(//g, '>').replace(/'/g, "'").replace(/"/g, '"'); - - /* - This function replaces CRLF by universal new line. - */ - universal_new_line = str => str.replace(/\r\n/g, "\n"); - - /* - This is the main function to put the script - content in the web interface. - */ - build() { - this.get_output_container(); - this.update_status(); - this.add_history(); - this.set_stderr_content_type(); - - if ((this.full_output + this.full_error + this.output_string + this - .error_string).length === 0 && this.add_history_) { - this.error_string = this.output.stderr = - "WebScripts warning: There is no output or " + - "error for this execution.\n"; - this.stderr_content_type = "text/plain"; - } - - this.add_stderr_content(); - this.build_content(); - this.add_content(); - this.add_to_download(); - this.theme(); - - return true; - } - - /* - This function builds a new container - or use the last builded container. - */ - get_output_container() { - if (this.make_new_output) { - this.code = this.code_builder(this.output, this.time); - this.new_output = this.new(this.code); - } else { - this.code = document.getElementById("code") || this - .code_builder(this.output, this.time); - this.new_output = document.getElementById("last_output") || this - .new(this.code); - } - } - - /* - This function updates the status at the - end of the script execution. - */ - update_status() { - if (this.update) { - this.code.innerText = this.code.innerText.replace('Running...', - this.output.code).replace('Running...', this.output - .error).replace('Running...', this.time); - } - } - - /* - This function adds a new script content in the history. - */ - add_history() { - if (this.add_history_) { - let history = new History(); - history.add(this); - } - } - - /* - This function sets the stderr - content type from the server response. - */ - set_stderr_content_type() { - if (this.output.hasOwnProperty("Stderr-Content-Type")) { - this.stderr_content_type = this.output["Stderr-Content-Type"]; - } - } - - /* - This function adds the stderr to the string content. - */ - add_stderr_content() { - if (this.error_string.length !== 0) { - if (this.stderr_content_type.includes("text/html")) { - this.html += this.anti_XSS(this.output.stderr); - } else if (this.stderr_content_type.includes("text/csv")) { - this.html += csv_to_html(this.output.stderr); - } else if (this.stderr_content_type.includes("text/json")) { - this.text += JSON.stringify(JSON.parse(this.output.stderr), - null, " "); - } else { - this.text += `\n${this.output.stderr}`; - } - } - } - - /* - This function sets the download parameters - and adds the stdout to the string content. - */ - build_content() { - let html_content = () => { - download_separator = "\n
\n"; - download_extension = ".html"; - download_type = "html"; - } - - let text_content = () => { - download_extension = ".txt"; - download_separator = "\n"; - download_type = "plain"; - } - - let add_text_output = (text) => { - this.text = this.make_new_output ? `\n${text}${this.text}` : - `${this.text}${text}`;; - } - - if (this.content_type.includes("text/html")) { - html_content(); - this.html = this.anti_XSS(this.output.stdout) + this.html; - } else if (this.content_type.includes("text/csv")) { - html_content(); - this.html = ParserCSV.prototype.csv_to_html(null, this.output - .stdout, '"', ',', '\r\n').outerHTML; - } else if (this.content_type.includes("text/json")) { - text_content(); - add_text_output(JSON.stringify(JSON.parse(this.output.stdout), - null, " ")); - } else { - text_content(); - add_text_output(this.output.stdout); - } - } - - /* - This function cleans and adds the string content to the container. - */ - add_content() { - this.code.innerText += this.universal_new_line(this.unescape(this - .text)); - this.new_output.innerHTML += this.html; - this.console_div.appendChild(this.new_output); - ShortTable.prototype.add_listeners(); - } - - /* - This function adds the string content to the download content. - */ - add_to_download() { - download_text += `${this.text}\n${this.html}${download_separator}`; - } - - /* - This function changes the color theme of the child elements of the - container. - */ - theme() { - if (localStorage.getItem('theme') === "light" || (localStorage - .getItem('theme') === null && !dark_theme)) { - Theme.prototype.change_elements('light', this.new_output); - } - /* else if (localStorage.getItem('theme') === null) { - change_theme( - class_name = 'default_theme', - element = this.new_output, - ); - }*/ - } - - /* - This function protects the browser against the XSS - vulnerabilities based on the user inputs only. - */ - anti_XSS(content) { - for (let value of Object.values(this.user_inputs)) { - value = value.value; - if (value.constructor.name !== "String" && value.constructor.name !== "Array") continue; - if (value.constructor.name !== "Array") value = [value]; - for (let v of value) { - let secure_value = this.escape(v); - if (v !== secure_value) { - content = content.replaceAll(v, secure_value); - } - } - } - - return content; - } -} - -/* -This function implements this history actions. -*/ -class History { - constructor() { - this.container = document.getElementById("webscripts_history"); - } - - /* - This function deletes the history content. - */ - clear() { - execution_number = 0; - this.container.innerText = ""; - } - - /* - This function display or hide history. - */ - change_display() { - let display = this.container.style.display; - - if (!display || display == "none") { - this.container.style.display = "inline-block"; - } else { - this.container.style.display = "none"; - - } - } - - /* - This function adds a script execution in history. - */ - add(output_builder) { - let button = document.createElement("button"); - - output_builder.add_history_ = false; - button.onclick = () => { - output_builder.get_output_container(); - output_builder.add_content(); - output_builder.add_to_download(); - output_builder.theme(); - } - - button.innerText = "Execution " + execution_number; - execution_number++; - this.container.appendChild(button); - - if (localStorage.getItem('theme') === "light") { - button.classList.toggle("light"); - } else if (localStorage.getItem('theme') === null) { - button.classList.toggle("default_theme"); - } - } -} - -/* -This class parses a CSV file. -*/ -class ParserCSV { - constructor(quote = '"', value_delimiter = ',', line_delimiter = "\n") { - let value_regex_string = - `(${quote}([^${quote}]|${quote}${quote})*${quote}|` + - `[^${quote}${value_delimiter}${line_delimiter}]*)`; - this.regex_line = new RegExp('((' + value_regex_string + - value_delimiter + ')*' + value_regex_string + '+|(' + - value_regex_string + value_delimiter + ')+' + - value_regex_string + ')', "gm"); - this.regex_value = new RegExp( - `((${quote}([^${quote}]|${quote}${quote})*${quote}|[^${quote}` + - `${value_delimiter}${line_delimiter}]+)|${value_delimiter})`, - "gm"); - } - - /* - This function parses CSV and build an HTML output. - */ - csv_to_html(headers, data, ...args) { - let csv_parser = new ParserCSV(...args); - let arrays = csv_parser.parse(data); - - headers = headers || arrays.shift(); - - let table = document.createElement("table"); - let thead = table.createTHead(); - let tbody = table.createTBody(); - - let line = document.createElement("tr"); - thead.appendChild(line); - - for (let header of headers) { - let column = document.createElement("th"); - line.appendChild(column); - column.innerText = header; - } - - for (let line_values of arrays) { - line = document.createElement("tr"); - tbody.appendChild(line); - for (let column_value of line_values) { - let column = document.createElement("td"); - line.appendChild(column); - column.innerText = column_value; - } - } - - return table; - } - - /* - This function parses a CSV file. - */ - parse(data) { - let lines = data.matchAll(this.regex_line); - - let arrays = []; - let array = []; - - for (let line of lines) { - let text_line = line[0]; - if (text_line) { - this.parse_line(text_line, array); - arrays.push(array); - array = []; - } - } - - if (array.length) { - arrays.push(array); - } - - return arrays; - } - - /* - This function parses a CSV line. - */ - parse_line(line, array) { - let values = line.matchAll(this.regex_value); - let not_empty = false; - - for (let value of values) { - let data = value[0]; - - if (data === ",") { - if (!not_empty) { - array.push(""); - } - not_empty = false; - continue; - } - - not_empty = true; - this.parse_value(data, array); - } - - if (!not_empty) { - array.push(""); - } - } - - /* - This function parses a CSV value. - */ - parse_value(data, array) { - if (data[0] === '"') { - array.push(data.substring(1, data.length - 1).replace('""', - '"')); - } else { - array.push(data); - } - } -} - -/* -This class adds an event listener on each -table header to shorts tables by values. -*/ -class ShortTable { - - /* - This function returns the column value. - */ - get_value(line, id) { - return line.children[id].innerText || line.children[id].textContent; - } - - /* - This function compares two values. - */ - compare(value1, value2) { - if (value1 !== '' && value2 !== '' && !isNaN(value1) && !isNaN( - value2)) { - return value1 - value2; - } else { - return value1.toString().localeCompare(value2) - } - } - - /* - This function generates the event listener callback. - */ - get_callback(id, ascendant) { - return function short_callback(line1, line2) { - if (!ascendant) { - let temp = line1; - line1 = line2; - line2 = temp; - } - - return ShortTable.prototype.compare(ShortTable.prototype - .get_value(line1, id), ShortTable.prototype - .get_value(line2, id)); - }; - } - - /* - This function shorts the table. - */ - event() { - let table = this.closest('table'); - let id = Array.from(this.parentNode.children).indexOf(this); - - Array.from(table.querySelectorAll('tbody > tr')) - .filter(tr => table == tr.closest('table')) - .sort(ShortTable.prototype.get_callback(id, window.ascendant = ! - window.ascendant)) - .forEach(line => line.parentNode.appendChild(line)); - } - - /* - This function adds listeners on each table headers. - */ - add_listeners() { - - Array.from(document.querySelectorAll('th')).forEach((header) => { - if (!header.have_short_event) { - header.addEventListener('click', ShortTable - .prototype.event.bind(header)); - header.innerText = "⋁ " + header.innerText; - header.have_short_event = true; - header.style.cursor = "pointer"; - setTimeout(() => { - let search = new TableSearch(header - .closest('table')) - }, 500); - } - }); - } -} - -/* -This class implements a tool to search lines in HTML table. -*/ -class TableSearch { - constructor(table) { - if (table.have_search) { - return; - } - - let is_light = document.getElementsByClassName('light').length; - - let input, parent; - this.table = table; - input = this.input = document.createElement('input'); - - parent = this.parent = table.parentNode; - input.type = "text"; - input.classList.add("webscripts_search_table"); - input.onchange = this.search.bind(this); - input.placeholder = "🔍 Search/Filter table values"; - table.have_search = true; - - this.selected_column = null; - parent.insertBefore(input, table); - - this.headers = table.getElementsByTagName('th'); - - if (is_light) input.classList.add('light'); - - this.add_selects(is_light); - } - - /* - This function adds a select box to headers to filter only on this column. - */ - add_selects(is_light) { - let counter = 0; - - for (let header of this.headers) { - let id = counter; - let select = document.createElement("span"); - select.innerText = "☐"; - select.classList.add("webscripts_column_select"); - if (is_light) select.classList.add('light'); - select.addEventListener('click', () => { - this.select_column(id); - }); - header.appendChild(select); - header.selected = false; - header.select = select; - - counter++; - } - } - - /* - This function unselects columns. - */ - unselect_column() { - this.selected_column = null; - - for (let header of this.headers) { - header.select.innerText = "☐"; - header.select.classList.remove("selected"); - header.select.classList.remove("unselected"); - } - - this.search(); - } - - /* - This function selects columns. - */ - select_column(id) { - if (this.selected_column !== null) { - this.unselect_column(); - return; - } - - this.selected_column = id; - let counter = 0; - - for (let header of this.headers) { - if (counter === id) { - header.select.innerText = "☑"; - header.select.classList.add("selected"); - } else { - header.select.innerText = "☒"; - header.select.classList.add("unselected"); - } - - counter++; - } - - this.search(); - } - - /* - This function searchs in table. - */ - search() { - let filter = this.input.value.toUpperCase(); - let lines = this.table.getElementsByTagName("tr"); - - for (let line of lines) { - let columns = line.getElementsByTagName("td"); - - if (!columns.length) { - continue; - } - - if (this.selected_column !== null) { - columns = [columns[this.selected_column]]; - } - - let is_matching = false; - for (let column of columns) { - let value = column.textContent || column.innerText; - - if (value.toUpperCase().indexOf(filter) > -1) { - is_matching = true; - } - } - - if (is_matching) { - line.style.display = ""; - } else { - line.style.display = "none"; - } - } - } -} diff --git a/static/pdf/WebScripts.pdf b/static/pdf/WebScripts.pdf deleted file mode 100644 index 001c1fbd..00000000 Binary files a/static/pdf/WebScripts.pdf and /dev/null differ diff --git a/static/templates/footer.html b/static/templates/footer.html deleted file mode 100644 index e6495379..00000000 --- a/static/templates/footer.html +++ /dev/null @@ -1,25 +0,0 @@ - \ No newline at end of file diff --git a/static/templates/header.html b/static/templates/header.html deleted file mode 100644 index e9f389e6..00000000 --- a/static/templates/header.html +++ /dev/null @@ -1,47 +0,0 @@ -
-
-

WebScripts Server

- -

This server can help administrators, SOC teams (Security Operations Center) and devops teams to deploy faster some scripts, to share some "console scripts" with people who have no particular computer knowledge and share environnements of scripts with their teams without install requirements on all computers of the team.

-
- -
-
- - -
- -
-
- - - - - - -
-
-
- - -
-
- -
-
\ No newline at end of file diff --git a/static/templates/index.html b/static/templates/index.html deleted file mode 100644 index e7cdefca..00000000 --- a/static/templates/index.html +++ /dev/null @@ -1,43 +0,0 @@ - - - - - - - Index of WebScripts Server - - - - - - - - - - - %(header)s - -
-
- - %(footer)s - - \ No newline at end of file diff --git a/static/templates/script.html b/static/templates/script.html deleted file mode 100644 index 71ecbc74..00000000 --- a/static/templates/script.html +++ /dev/null @@ -1,82 +0,0 @@ - - - - - - - %(name)s of WebScripts Server - - - - - - - - - - - %(header)s - -
-
-

Script: %(name)s

-

Documentation -
- -

Welcome on script "%(name)s" user "%(user)s" !

- -
-
-
-
- -
- -
-
- -
- -
- -
-
-
- - - -
- -
- -
-
- - %(footer)s - - - - \ No newline at end of file diff --git a/utils.py b/utils.py deleted file mode 100644 index c933d21e..00000000 --- a/utils.py +++ /dev/null @@ -1,1277 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -################### -# This tool runs CLI scripts and displays output in a Web Interface. -# Copyright (C) 2021, 2022, 2023 Maurice Lambert - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. - -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. - -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -################### - -""" -This tool runs CLI scripts and displays output in a Web Interface. - -This file implements some tools for WebScripts server -and scripts (Logs, Namespace for configuration, ...). -""" - -__version__ = "1.0.2" -__author__ = "Maurice Lambert" -__author_email__ = "mauricelambert434@gmail.com" -__maintainer__ = "Maurice Lambert" -__maintainer_email__ = "mauricelambert434@gmail.com" -__description__ = """ -This tool run scripts and display the result in a Web -Interface. - -This file implements some tools for WebScripts server -and scripts (Logs, Namespace for configuration, ...). -""" -license = "GPL-3.0 License" -__url__ = "https://github.com/mauricelambert/WebScripts" - -copyright = """ -WebScripts Copyright (C) 2021, 2022, 2023 Maurice Lambert -This program comes with ABSOLUTELY NO WARRANTY. -This is free software, and you are welcome to redistribute it -under certain conditions. -""" -__license__ = license -__copyright__ = copyright - -__all__ = [ - "Logs", - "DefaultNamespace", - "log_trace", - "get_ip", - "get_arguments_count", - "get_file_content", - "get_real_path", - "get_encodings", - "get_ini_dict", - "server_path", - "CustomLogHandler", - # "doRollover", - # "rotator", - # "namer", - "logger_debug", - "logger_info", - "logger_auth", - "logger_access", - "logger_response", - "logger_command", - "logger_warning", - "logger_error", - "logger_critical", - "check_file_permission", -] - -from typing import ( - TypeVar, - List, - Dict, - _SpecialGenericAlias, - _GenericAlias, - Any, - Union, -) -from logging import ( - log as log_, - debug, - info, - warning, - error, - critical, - exception, - addLevelName, - basicConfig, - Logger, - getLogger, -) -from types import ( - SimpleNamespace, - FunctionType, - MethodType, - CodeType, - FrameType, -) -from os import path, _Environ, device_encoding, remove, stat, getcwd, listdir -from os.path import abspath, isdir, isfile, exists, dirname, normcase, join -from subprocess import check_call, DEVNULL # nosec -from locale import getpreferredencoding -from configparser import ConfigParser -from collections.abc import Callable -from platform import system -from getpass import getuser -from functools import wraps -from sys import _getframe -from stat import filemode -from gzip import compress -from json import dump -import logging.handlers - -if __package__: - from .Errors import ( - WebScriptsConfigurationError, - WebScriptsArgumentError, - MissingAttributesError, - WebScriptsConfigurationTypeError, - WebScriptsSecurityError, - ) -else: - from Errors import ( - WebScriptsConfigurationError, - WebScriptsArgumentError, - MissingAttributesError, - WebScriptsConfigurationTypeError, - WebScriptsSecurityError, - ) - -StrOrBytes = TypeVar("StrOrBytes", str, bytes) -DefaultNamespace = TypeVar("DefaultNamespace") - -utils_filename: str = join("WebScripts", "utils.py") -logging_filename: str = join("Lib", "logging") -test_frame: Callable = ( - lambda f, l: (f.endswith(utils_filename) and l < 703) - or logging_filename in f -) - - -def get_log_frame() -> FrameType: - """ - This function returns the frame - to get file and line of the log call. - """ - - filename: str = utils_filename - counter: int = 4 - line: int = 0 - - while test_frame(filename, line): - frame: FrameType = _getframe(counter) - filename: str = frame.f_code.co_filename - line: int = frame.f_lineno - counter += 1 - - return _getframe(counter - 2) - - -logging.currentframe = get_log_frame # lambda: _getframe(5) - -IS_WINDOWS = system() == "Windows" -IP_HEADERS = [ - "HTTP_X_FORWARDED_FOR", - "HTTP_X_REAL_IP", - "HTTP_X_FORWARDED_HOST", - "HTTP_CLIENT_IP", - "REMOTE_ADDR", -] - - -class _Logs: - """ - This class implements basic python logs. - """ - - console: Logger = getLogger("WebScripts.console") - file: Logger = getLogger("WebScripts.file") - - log_debug: Logger = getLogger("WebScripts.debug") - log_info: Logger = getLogger("WebScripts.info") - log_warning: Logger = getLogger("WebScripts.warning") - log_error: Logger = getLogger("WebScripts.error") - log_critical: Logger = getLogger("WebScripts.critical") - - log_trace: Logger = getLogger("WebScripts.trace") - log_authentication: Logger = getLogger("WebScripts.authentication") - log_access: Logger = getLogger("WebScripts.access") - log_response: Logger = getLogger("WebScripts.response") - log_command: Logger = getLogger("WebScripts.command") - - def debug(log: str) -> None: - """ - This function implements basic python debug logs for WebScripts. - """ - - logs_log_debug_debug(log) - logs_console_debug(f"\x1b[32m{log}\x1b[0m") - logs_file_debug(log) - debug(log) - - def info(log: str) -> None: - """ - This function implements basic python info logs for WebScripts. - """ - - logs_log_info_info(log) - logs_console_info(f"\x1b[34m{log}\x1b[0m") - logs_file_info(log) - info(log) - - def warning(log: str) -> None: - """ - This function implements basic python warning logs for WebScripts. - """ - - logs_log_warning_warning(log) - logs_console_warning(f"\x1b[33m{log}\x1b[0m") - logs_file_warning(log) - warning(log) - - def error(log: str) -> None: - """ - This function implements basic python error logs for WebScripts. - """ - - logs_log_error_error(log) - logs_console_error(f"\x1b[35m{log}\x1b[0m") - logs_file_error(log) - error(log) - - def critical(log: str) -> None: - """ - This function implements basic python critical logs for WebScripts. - """ - - logs_log_critical_critical(log) - logs_console_critical(f"\x1b[31m{log}\x1b[0m") - logs_file_critical(log) - critical(log) - - def exception(log: str) -> None: - """ - This function implements basic python exception (error) logs for - WebScripts. - """ - - logs_log_error_exception(log) - logs_console_exception(log) - logs_file_exception(log) - exception(log) - - def trace(log: str) -> None: - """ - This function implements trace logs for WebScripts. - """ - - logs_log_trace_log(5, log) - log_(5, log) - - def authentication(log: str) -> None: - """ - This function implements authentication logs for WebScripts. - """ - - logs_log_info_info(log) - logs_log_auth_log(24, log) - logs_console_log(24, f"\x1b[36m{log}\x1b[0m") - logs_file_log(24, log) - log_(24, log) - - def access(log: str) -> None: - """ - This function implements access logs for WebScripts. - """ - - logs_log_debug_debug(log) - logs_log_access_log(25, log) - logs_console_log(25, f"\x1b[36m{log}\x1b[0m") - logs_file_log(25, log) - log_(25, log) - - def response(log: str) -> None: - """ - This function implements response logs for WebScripts. - """ - - logs_log_debug_debug(log) - logs_log_response_log(26, log) - logs_console_log(26, f"\x1b[36m{log}\x1b[0m") - logs_file_log(26, log) - log_(26, log) - - def command(log: str) -> None: - """ - This function implements response logs for WebScripts. - """ - - logs_log_info_info(log) - logs_log_command_log(27, log) - logs_console_log(27, f"\x1b[36m{log}\x1b[0m") - logs_file_log(27, log) - log_(27, log) - - def config(*args, **kwargs): - """ - This function config ROOT logger. - """ - - basicConfig(*args, **kwargs) - - -class WindowsLogs(_Logs): - """ - This class log on Windows. - """ - - app: str = __package__ or "WebScripts" - - def authentication(log: str) -> None: - """ - This function logs authentication on Windows. - """ - - super(WindowsLogs, WindowsLogs).authentication(log) - ReportEvent( - WindowsLogs.app, - 0x960, - eventCategory=1, - eventType=EVENTLOG_INFORMATION_TYPE, - strings=[log], - data=log.encode(), - sid=SID, - ) - - def access(log: str) -> None: - """ - This function logs access on Windows. - """ - - super(WindowsLogs, WindowsLogs).access(log) - ReportEvent( - WindowsLogs.app, - 0x9C4, - eventCategory=1, - eventType=EVENTLOG_INFORMATION_TYPE, - strings=[log], - data=log.encode(), - sid=SID, - ) - - def response(log: str) -> None: - """ - This function logs response on Windows. - """ - - super(WindowsLogs, WindowsLogs).response(log) - ReportEvent( - WindowsLogs.app, - 0xA28, - eventCategory=1, - eventType=EVENTLOG_INFORMATION_TYPE, - strings=[log], - data=log.encode(), - sid=SID, - ) - - def command(log: str) -> None: - """ - This function logs commands on Windows. - """ - - super(WindowsLogs, WindowsLogs).command(log) - ReportEvent( - WindowsLogs.app, - 0xA8C, - eventCategory=1, - eventType=EVENTLOG_INFORMATION_TYPE, - strings=[log], - data=log.encode(), - sid=SID, - ) - - def debug(log: str) -> None: - """ - This function logs debugs on Windows. - """ - - super(WindowsLogs, WindowsLogs).debug(log) - ReportEvent( - WindowsLogs.app, - 0x3E8, - eventCategory=1, - eventType=EVENTLOG_INFORMATION_TYPE, - strings=[log], - data=log.encode(), - sid=SID, - ) - - def info(log: str) -> None: - """ - This function logs infos on Windows. - """ - - super(WindowsLogs, WindowsLogs).info(log) - ReportEvent( - WindowsLogs.app, - 0x7D0, - eventCategory=1, - eventType=EVENTLOG_INFORMATION_TYPE, - strings=[log], - data=log.encode(), - sid=SID, - ) - - def warning(log: str) -> None: - """ - This function logs warnings on Windows. - """ - - super(WindowsLogs, WindowsLogs).warning(log) - ReportEvent( - WindowsLogs.app, - 0xBB8, - eventCategory=1, - eventType=EVENTLOG_WARNING_TYPE, - strings=[log], - data=log.encode(), - sid=SID, - ) - - def error(log: str) -> None: - """ - This function logs errors on Windows. - """ - - super(WindowsLogs, WindowsLogs).error(log) - ReportEvent( - WindowsLogs.app, - 0xFA0, - eventCategory=1, - eventType=EVENTLOG_ERROR_TYPE, - strings=[log], - data=log.encode(), - sid=SID, - ) - - def critical(log: str) -> None: - """ - This function logs criticals on Windows. - """ - - super(WindowsLogs, WindowsLogs).critical(log) - ReportEvent( - WindowsLogs.app, - 0x1388, - eventCategory=1, - eventType=EVENTLOG_ERROR_TYPE, - strings=[log], - data=log.encode(), - sid=SID, - ) - - -class LinuxLogs(_Logs): - """ - This class logs on Linux. - """ - - def authentication(log: str) -> None: - """ - This function logs authentication on Linux. - """ - - super(LinuxLogs, LinuxLogs).authentication(log) - ReportEvent(LOG_INFO, log) - - def access(log: str) -> None: - """ - This function logs access on Linux. - """ - - super(LinuxLogs, LinuxLogs).access(log) - ReportEvent(LOG_INFO, log) - - def response(log: str) -> None: - """ - This function logs response on Linux. - """ - - super(LinuxLogs, LinuxLogs).response(log) - ReportEvent(LOG_INFO, log) - - def command(log: str) -> None: - """ - This function logs command on Linux. - """ - - super(LinuxLogs, LinuxLogs).command(log) - ReportEvent(LOG_INFO, log) - - def debug(log: str) -> None: - """ - This function logs debugs on Linux. - """ - - super(LinuxLogs, LinuxLogs).debug(log) - ReportEvent(LOG_DEBUG, log) - - def info(log: str) -> None: - """ - This function logs infos on Linux. - """ - - super(LinuxLogs, LinuxLogs).info(log) - ReportEvent(LOG_INFO, log) - - def warning(log: str) -> None: - """ - This function logs warnings on Linux. - """ - - super(LinuxLogs, LinuxLogs).warning(log) - ReportEvent(LOG_WARNING, log) - - def error(log: str) -> None: - """ - This function logs errors on Linux. - """ - - super(LinuxLogs, LinuxLogs).error(log) - ReportEvent(LOG_ERR, log) - - def critical(log: str) -> None: - """ - This function logs criticals on Linux. - """ - - super(LinuxLogs, LinuxLogs).critical(log) - ReportEvent(LOG_CRIT, log) - - -def log_trace( - function: Union[FunctionType, MethodType] -) -> Union[FunctionType, MethodType]: - """ - This decorator traces functions (start and end). - """ - - @wraps(function) - def wrapper(*args, **kwds): - # if (isinstance(function, classmethod) - # or isinstance(function, staticmethod)): - # name = function.__func__.__name__ - # else: - # name = function.__name__ - - logger_trace(f"Start {function.__name__}...") - values = function(*args, **kwds) - logger_trace(f"End of {function.__name__}.") - return values - - return wrapper - - -class CustomLogHandler(logging.handlers.RotatingFileHandler): - """ - This class is a custom logging handler. - """ - - def doRollover(self): - """ - Do a rollover, as described in __init__(). - """ - - if self.stream: - self.stream.close() - self.stream = None - if self.backupCount > 0: - filename = self.baseFilename - i = 0 - while exists(filename): - i += 1 - filename = self.rotation_filename( - "%s.%d" % (self.baseFilename, i) - ) - - self.rotate(self.baseFilename, filename) - if not self.delay: - self.stream = self._open() - - def namer(self, name: str) -> str: - """ - This function returns the new name of the old log files. - """ - - return f"{name}.gz" - - def rotator(self, source: str, destination: str) -> None: - """ - This function compresses old log files. - """ - - with open(source, "rb") as source_file: - data = source_file.read() - compressed = compress(data, 9) - - with open(destination, "wb") as destination_file: - destination_file.write(compressed) - - remove(source) - - -logging.handlers.CustomLogHandler = CustomLogHandler - -if IS_WINDOWS: - try: - from win32con import TOKEN_READ - from win32api import GetCurrentProcess - from win32evtlogutil import ReportEvent - from win32security import ( - OpenProcessToken, - GetTokenInformation, - TokenUser, - ) - from win32evtlog import ( - EVENTLOG_INFORMATION_TYPE, - EVENTLOG_WARNING_TYPE, - EVENTLOG_ERROR_TYPE, - ) - except ImportError: - WINDOWS_LOGS = False - else: - WINDOWS_LOGS = True - SID = GetTokenInformation( - OpenProcessToken(GetCurrentProcess(), TOKEN_READ), TokenUser - )[0] - - check_call( - [ - r"C:\WINDOWS\system32\reg.exe", - "add", - r"HKEY_CURRENT_USER\Console", - "/v", - "VirtualTerminalLevel", - "/t", - "REG_DWORD", - "/d", - "0x00000001", - "/f", - ], - stdout=DEVNULL, - stderr=DEVNULL, - ) # Active colors in console (for logs) # nosec -else: - from pwd import getpwuid - from syslog import ( - syslog as ReportEvent, - LOG_DEBUG, - LOG_INFO, - LOG_WARNING, - LOG_ERR, - LOG_CRIT, - ) - - Logs = LinuxLogs - - -if IS_WINDOWS and WINDOWS_LOGS: - Logs = WindowsLogs -elif IS_WINDOWS: - Logs = _Logs - -logs_log_debug_debug: Callable = Logs.log_debug.debug -logs_console_debug: Callable = Logs.console.debug -logs_file_debug: Callable = Logs.file.debug -logs_log_info_info: Callable = Logs.log_info.info -logs_console_info: Callable = Logs.console.info -logs_file_info: Callable = Logs.file.info -logs_log_warning_warning: Callable = Logs.log_warning.warning -logs_console_warning: Callable = Logs.console.warning -logs_file_warning: Callable = Logs.file.warning -logs_log_error_error: Callable = Logs.log_error.error -logs_console_error: Callable = Logs.console.error -logs_file_error: Callable = Logs.file.error -logs_log_critical_critical: Callable = Logs.log_critical.critical -logs_console_critical: Callable = Logs.console.critical -logs_file_critical: Callable = Logs.file.critical -logs_log_error_exception: Callable = Logs.log_error.exception -logs_console_exception: Callable = Logs.console.exception -logs_file_exception: Callable = Logs.file.exception -logs_log_trace_log: Callable = Logs.log_trace.log -logs_log_auth_log: Callable = Logs.log_authentication.log -logs_log_access_log: Callable = Logs.log_access.log -logs_log_response_log: Callable = Logs.log_response.log -logs_log_command_log: Callable = Logs.log_command.log -logs_console_log: Callable = Logs.console.log -logs_file_log: Callable = Logs.file.log - - -if IS_WINDOWS and not WINDOWS_LOGS: - Logs.error("PyWin32 is not installed, no Windows Event Logs.") - - -class DefaultNamespace(SimpleNamespace): - """ - This class build simple namespace with default - attributs. - """ - - def __init__( - self, - required: List[str] = [], - optional: List[str] = [], - default: dict = {}, - types: dict = {}, - ): - self.__required__ = required or getattr(self, "__required__", []) - self.__optional__ = optional or getattr(self, "__optional__", []) - self.__default__ = default or getattr(self, "__default__", {}) - self.__types__ = types or getattr(self, "__types__", {}) - - for attr, value in self.__default__.items(): - if getattr(self, attr, None) is None: - setattr(self, attr, value) - - @log_trace - def update(self, **kwargs): - """ - This function add/update attributes with **kwargs arguments. - """ - - self.__dict__.update(kwargs) - - @log_trace - def check_required(self) -> None: - """ - This function checks required attributes - if one of required attributes is missing this - function raise MissingAttributesError. - """ - - for attribut in self.__required__: - if getattr(self, attribut, None) is None: - raise MissingAttributesError( - f"{attribut} is missing in {self.__class__.__name__}." - ) - - @log_trace - def get_missings(self) -> List[str]: - """ - This function checks required attributes - and return a List[str] of missing required attributes. - """ - - missings = [] - - for attribut in self.__required__: - if getattr(self, attribut, None) is None: - missings.append(attribut) - - return missings - - @log_trace - def get_unexpecteds(self, log: bool = True) -> List[str]: - """ - This function return a List[str] of - all attributes not in optional and - required attributes. - - If log argument is True a Warning log message is - write for all unexpected attributes. - """ - - all_ = self.__required__ + self.__optional__ - unexpecteds = [] - - for attribut in self.get_dict().keys(): - if attribut not in all_: - unexpecteds.append(attribut) - - if log: - logger_warning( - f"{attribut} is an unexpected argument " - f"in {self.__class__.__name__}" - ) - - return unexpecteds - - @log_trace - def get_dict(self) -> None: - """ - This function return a dict of attributes. - """ - - dict_ = self.__dict__.copy() - - to_delete = [] - - for attribut, value in dict_.items(): - if isinstance(value, MethodType) or ( - attribut.startswith("__") and attribut.endswith("__") - ): - to_delete.append(attribut) - - for attribut in to_delete: - del dict_[attribut] - - return dict_ - - @log_trace - def export_as_json(self, name: str = None) -> None: - """ - This function export namespace values (useful for debugging). - """ - - if name is None: - name = f"export_{self.__class__.__name__}.json" - - export = self.get_dict() - - with open(name, "w") as file: - dump(export, file, indent=4) - - @log_trace - def build_types(self) -> None: - """ - This function builds type from configuration values. - """ - - for attribut, type_ in self.__types__.items(): - value = getattr(self, attribut, None) - - if value is None: - continue - - self.build_type(attribut, value, type_) - - @log_trace - def build_type( - self, attribut: str, value: Any, type_: type = None - ) -> None: - """ - This function builds type from configuration value. - """ - - def get_number( - functype: type, value: str, attribut: str - ) -> Union[int, float]: - if functype is float: - typed = value.replace(".", "") - else: - typed = value - if typed.isdigit(): - return functype(value) - else: - raise WebScriptsConfigurationError( - f"{attribut} must be a list of number (" - f"{functype.__name__}) but contain {value!r}" - ) - - if type_ is None: - type_ = self.__types__.get(attribut) - - if type_ is None or type_ is str: - setattr(self, attribut, str(value)) - return None - - if isinstance(type_, _GenericAlias) or isinstance( - type_, _SpecialGenericAlias - ): - if isinstance(value, type_.__origin__): - setattr(self, attribut, value) - # return None - else: - if isinstance(value, type_): - setattr(self, attribut, value) - return None - - if type_ is bool: - if value == "true": - setattr(self, attribut, True) - elif value == "false": - setattr(self, attribut, False) - else: - raise WebScriptsConfigurationError( - f"{attribut} must be boolean (true or false)" - f" but is {value}" - ) - - elif type_ is int or type_ is float: - if isinstance(value, str): - setattr(self, attribut, get_number(type_, value, attribut)) - elif type_ is float and isinstance(value, int): - setattr(self, attribut, float(value)) - else: - raise WebScriptsConfigurationError( - f"{attribut!r} must be an integer but is {value!r}" - ) - - elif type_ is List[str] or type_ is list or type_ is List: - if isinstance(value, str): - setattr(self, attribut, value.split(",")) - elif isinstance(value, list): - type_list = [] - for element in value: - if isinstance(element, str): - type_list.append(element) - else: - raise WebScriptsConfigurationError( - f"{attribut} must be a list of strings" - f" but contain {element!r}" - ) - setattr(self, attribut, type_list) - - elif type_ is List[int] or type_ is List[float]: - functype = type_.__args__[0] - type_list = [] - - if isinstance(value, str): - for typed in value.split(","): - type_list.append(get_number(functype, typed, attribut)) - elif isinstance(value, functype) or ( - functype is float and isinstance(value, int) - ): - type_list.append(functype(value)) - elif isinstance(value, list): - for element in value: - if isinstance(element, str): - type_list.append( - get_number(functype, element, attribut) - ) - elif isinstance(element, functype) or ( - functype is float and isinstance(element, int) - ): - type_list.append(functype(element)) - else: - raise WebScriptsConfigurationError( - f"{attribut} must be a list of number (" - f"{functype.__name__}) but contain {element!r}" - ) - else: - raise WebScriptsConfigurationError( - f"{attribut} must be a list of number (" - f"{functype.__name__}) but is {value!r}" - ) - - setattr(self, attribut, type_list) - - @log_trace - def set_defaults(self) -> None: - """ - This function set defaults attribut with defaults values. - """ - - for attr, value in self.__defaults__.items(): - setattr(self, attr, getattr(self, attr, value)) - - @log_trace - def get(self, key: str, default=None): - """ - Compatibility with dict. - """ - - return getattr(self, key, default) - - @log_trace - def __getitem__(self, key: str): - """ - Compatibility with dict. - """ - - return getattr(self, key) - - @classmethod - @log_trace - def default_build(cls, **kwargs) -> DefaultNamespace: - """ - Default build for DefaultNamespace (set defaults, add values, - check requirements and unexpected values and build types). - """ - - namespace = cls() - namespace.set_defaults() - namespace.update(**kwargs) - namespace.check_required() - namespace.get_unexpecteds() - namespace.build_types() - return namespace - - -@log_trace -def get_encodings(): - """ - This function returns the probable encodings. - """ - - encoding = getpreferredencoding() - if encoding is not None: - yield encoding - - encoding = device_encoding(0) - if encoding is not None: - yield encoding - - yield "utf-8" # Default for Linux - yield "cp1252" # Default for Windows - yield "latin-1" # Can read all files - yield None - - -@log_trace -def get_ini_dict(filename: str) -> Dict[str, Dict[str, str]]: - """ - This function return a dict from ini filename. - """ - - config = ConfigParser(allow_no_value=True, inline_comment_prefixes="#") - config.read(filename) - return config._sections - - -@log_trace -def get_ip( - environ: _Environ, ip_number: int = None, protected: bool = True -) -> str: - """ - This function return the real IP. - """ - - ips = None - counter = 0 - check_number = ip_number is not None - - for ip_header in IP_HEADERS: - ip = environ.get(ip_header) - if ip is not None: - ip_length = len(ip.split(", ")) - if protected: - counter += ip_length - - if check_number and counter > ip_number: - logger_critical(f"IP Spoofing detected: {ips}, {ip}.") - return None - - if ips: - ips += ", " + ip - else: - ips = ip - - logger_debug(f"Header: {ip_header!r} found with ip: {ip!r}") - else: - ips = ip - break - - return ips - - -@log_trace -def get_file_content( - file_path, *args, as_iterator: bool = False, **kwargs -) -> StrOrBytes: - """ - This function return the file content. - """ - - if as_iterator: - return open(get_real_path(file_path), "rb", *args, **kwargs) - - if "encoding" in kwargs or "rb" in args or "rb" in kwargs.values(): - with open(get_real_path(file_path), *args, **kwargs) as file: - content = file.read() - return content - - errors = [] - encodings = get_encodings() - encoding = next(encodings) - content = None - - while encoding is not None: - file = open( - get_real_path(file_path), *args, encoding=encoding, **kwargs - ) - try: - content = file.read() - except UnicodeDecodeError as e: - errors.append(e) - else: - success = True - finally: - success = False if content is None else True - file.close() - - if success: - return content - - encoding = next(encodings) - - raise Exception(errors) - - -@log_trace -def get_arguments_count(object_: Callable): - """ - This function return the number of argument to call this Callable object. - """ - - obj__get_attr = object_ - - for attrs in [ - ["__wrapped__", "__code__"], - ["__class__", "__call__", "__wrapped__", "__code__"], - ["__class__", "__call__", "__code__"], - ["__code__"], - ["__func__", "__code__"], - ]: - for attr in attrs: - obj__get_attr = getattr(obj__get_attr, attr, None) - if obj__get_attr is None: - break - - if isinstance(obj__get_attr, CodeType): - return obj__get_attr.co_argcount + obj__get_attr.co_kwonlyargcount - - obj__get_attr = object_ - - return 7 - - -@log_trace -def get_real_path( - file_path: str, is_dir: bool = False, no_error: bool = False -) -> str: - """ - This function return the real path for files. - """ - - if file_path is None: - return file_path - - if IS_WINDOWS: - length = 2 - index = 1 - character = ":" - else: - length = 1 - index = 0 - character = "/" - - file_path = normcase(file_path) - server_file_path = join(server_path, file_path) - - if is_dir: - check = isdir - else: - check = isfile - - if check(file_path): - return abspath(file_path) - elif ( - len(file_path) > length - and file_path[index] != character - and check(server_file_path) - ): - return abspath(server_file_path) - elif no_error: - return None - - raise FileNotFoundError( - f"[WebScripts] No such file or directory: '{file_path}'" - ) - - -@log_trace -def check_file_permission( - configuration: DefaultNamespace, - filepath: str, - recursive: bool = False, - executable: bool = False, - dir_check: bool = True, -) -> bool: - """ - This function checks files and directories permissions for security. - """ - - if not IS_WINDOWS and configuration.force_file_permissions: - metadata = stat(filepath) - mode = filemode(metadata.st_mode) - owner = getpwuid(metadata.st_uid).pw_name - - # frame = _getframe(1) - # frame = frame.f_back - # code = frame.f_code - - # print( - # "\x1b[31m", - # "*" * 50, - # filepath + "\t-\t" + code.co_filename - # + ":" + str(frame.f_lineno) + ":" - # + code.co_name + "\x1b[0m", - # sep="\n" - # ) - - if isfile(filepath): - mode1 = mode[1] - mode3 = mode[3] - - return_value = ( - mode[0] == "-" - and (mode1 == "r" or mode1 == "-") - and mode[2] == "-" - and ((executable and mode3 == "x") or mode3 == "-") - and mode.endswith("------") - and owner == user - ) - - if return_value and dir_check: - return_value = check_file_permission( - configuration, dirname(filepath) - ) - - elif isdir(filepath): - return_value = mode == "drwxr-xr-x" and owner == "root" - - if recursive and return_value: - return_value = all( - check_file_permission( - configuration, x, recursive, executable, False - ) - for x in listdir(filepath) - ) - - if not return_value: - logger_critical( - "File permissions are not secure: " - f"{filepath!r} (owner: {owner!r}, permissions: {mode})" - ) - - return return_value - - else: - return True - - -server_path: str = dirname(__file__) -working_directory: str = getcwd() - -user: str = getuser() - -date_format: str = "%Y-%m-%d %H:%M:%S" -addLevelName(5, "TRACE") -addLevelName(24, "AUTH") -addLevelName(25, "ACCESS") -addLevelName(26, "RESPONSE") -addLevelName(27, "COMMAND") - -logger_trace: Callable = Logs.trace -logger_debug: Callable = Logs.debug -logger_info: Callable = Logs.info -logger_auth: Callable = Logs.authentication -logger_access: Callable = Logs.access -logger_response: Callable = Logs.response -logger_command: Callable = Logs.command -logger_warning: Callable = Logs.warning -logger_error: Callable = Logs.error -logger_critical: Callable = Logs.critical