diff --git a/tools/server/public_simplechat/local.tools/config.py b/tools/server/public_simplechat/local.tools/config.py new file mode 100644 index 0000000000..f452ade9af --- /dev/null +++ b/tools/server/public_simplechat/local.tools/config.py @@ -0,0 +1,148 @@ +# Config entries +# by Humans for All +# + +from dataclasses import dataclass +from typing import Any +import http.server +import ssl +import sys +import urlvalidator as mUV +import debug as mDebug + + +gConfigNeeded = [ 'acl.schemes', 'acl.domains', 'sec.bearer' ] + + +@dataclass(frozen=True) +class Sec(dict): + """ + Used to store security related config entries + """ + certFile: str = "" + keyFile: str = "" + bearerAuth: str = "" + +@dataclass +class ACL(dict): + schemes: list[str] = [] + domains: list[str] = [] + +@dataclass +class Network(dict): + port: int = 3128 + addr: str = '' + +@dataclass +class Op(dict): + configFile: str = "/dev/null" + debug: bool = False + server: http.server.ThreadingHTTPServer|None = None + sslContext: ssl.SSLContext|None = None + bearerTransformed: str = "" + bearerTransformedYear: str = "" + +@dataclass +class Config(dict): + op: Op = Op() + sec: Sec = Sec() + acl: ACL = ACL() + nw: Network = Network() + + def get_type(self, keyTree: str): + cKeyList = keyTree.split('.') + cur = self + for k in cKeyList[:-1]: + cur = self[k] + return type(cur[cKeyList[-1]]) + + def get_value(self, keyTree: str): + cKeyList = keyTree.split('.') + cur = self + for k in cKeyList[:-1]: + cur = self[k] + return cur[cKeyList[-1]] + + def set_value(self, keyTree: str, value: Any): + cKeyList = keyTree.split('.') + cur = self + for k in cKeyList[:-1]: + cur = self[k] + cur[cKeyList[-1]] = value + + def validate(self): + for k in gConfigNeeded: + if self.get_value(k) == None: + print(f"ERRR:ProcessArgs:Missing:{k}:did you forget to pass the config file...") + exit(104) + mDebug.setup(self.op.debug) + if (self.acl.schemes and self.acl.domains): + mUV.validator_setup(self.acl.schemes, self.acl.domains) + + def load_config(self, configFile: str): + """ + Allow loading of a json based config file + + The config entries should be named same as their equivalent cmdline argument + entries but without the -- prefix. + + As far as the logic is concerned the entries could either come from cmdline + or from a json based config file. + """ + import json + self.op.configFile = configFile + with open(self.op.configFile) as f: + cfgs: dict[str, Any] = json.load(f) + for cfg in cfgs: + print(f"DBUG:LoadConfig:{cfg}") + try: + neededType = self.get_type(cfg) + gotValue = cfgs[cfg] + gotType = type(gotValue) + if gotType.__name__ != neededType.__name__: + print(f"ERRR:LoadConfig:{cfg}:expected type [{neededType}] got type [{gotType}]") + exit(112) + self.set_value(cfg, gotValue) + except KeyError: + print(f"ERRR:LoadConfig:{cfg}:UnknownCommand") + exit(113) + + def process_args(self, args: list[str]): + """ + Helper to process command line arguments. + + Flow setup below such that + * location of --config in commandline will decide whether command line or config file will get + priority wrt setting program parameters. + * str type values in cmdline are picked up directly, without running them through ast.literal_eval, + bcas otherwise one will have to ensure throught the cmdline arg mechanism that string quote is + retained for literal_eval + """ + import ast + print(self) + iArg = 1 + while iArg < len(args): + cArg = args[iArg] + if (not cArg.startswith("--")): + print(f"ERRR:ProcessArgs:{iArg}:{cArg}:MalformedCommandOr???") + exit(101) + cArg = cArg[2:] + print(f"DBUG:ProcessArgs:{iArg}:{cArg}") + try: + aTypeCheck = self.get_type(cArg) + aValue = args[iArg+1] + if aTypeCheck.__name__ != 'str': + aValue = ast.literal_eval(aValue) + aType = type(aValue) + if aType.__name__ != aTypeCheck.__name__: + print(f"ERRR:ProcessArgs:{iArg}:{cArg}:expected type [{aTypeCheck}] got type [{aType}]") + exit(102) + self.set_value(cArg, aValue) + iArg += 2 + if cArg == 'op.configFile': + self.load_config(aValue) + except KeyError: + print(f"ERRR:ProcessArgs:{iArg}:{cArg}:UnknownCommand:{sys.exception()}") + exit(103) + print(self) + self.validate() diff --git a/tools/server/public_simplechat/local.tools/simpleproxy.py b/tools/server/public_simplechat/local.tools/simpleproxy.py index ca2ffaad28..cdaf6e4d38 100644 --- a/tools/server/public_simplechat/local.tools/simpleproxy.py +++ b/tools/server/public_simplechat/local.tools/simpleproxy.py @@ -25,33 +25,13 @@ import time import ssl import traceback from typing import Callable -import urlvalidator as uv import pdfmagic as mPdf import webmagic as mWeb -import debug as mDebug +import config as mConfig -gMe = { - '--port': 3128, - '--config': '/dev/null', - '--debug': False, - 'bearer.transformed.year': "", - 'server': None, - 'sslContext': None, -} +gMe = mConfig.Config() -gConfigType = { - '--port': 'int', - '--config': 'str', - '--debug': 'bool', - '--allowed.schemes': 'list', - '--allowed.domains': 'list', - '--bearer.insecure': 'str', - '--sec.keyfile': 'str', - '--sec.certfile': 'str' -} - -gConfigNeeded = [ '--allowed.schemes', '--allowed.domains', '--bearer.insecure' ] gAllowedCalls = { "xmlfiltered": [], @@ -68,13 +48,13 @@ def bearer_transform(): """ global gMe year = str(time.gmtime().tm_year) - if gMe['bearer.transformed.year'] == year: + if gMe.op.bearerTransformedYear == year: return import hashlib s256 = hashlib.sha256(year.encode('utf-8')) - s256.update(gMe['--bearer.insecure'].encode('utf-8')) - gMe['--bearer.transformed'] = s256.hexdigest() - gMe['bearer.transformed.year'] = year + s256.update(gMe.sec.bearerAuth.encode('utf-8')) + gMe.op.bearerTransformed = s256.hexdigest() + gMe.op.bearerTransformedYear = year class ProxyHandler(http.server.BaseHTTPRequestHandler): @@ -218,83 +198,6 @@ def handle_aum(ph: ProxyHandler, pr: urllib.parse.ParseResult): ph.end_headers() -def load_config(): - """ - Allow loading of a json based config file - - The config entries should be named same as their equivalent cmdline argument - entries but without the -- prefix. They will be loaded into gMe after adding - -- prefix. - - As far as the program is concerned the entries could either come from cmdline - or from a json based config file. - """ - global gMe - import json - with open(gMe['--config']) as f: - cfg = json.load(f) - for k in cfg: - print(f"DBUG:LoadConfig:{k}") - try: - cArg = f"--{k}" - aTypeCheck = gConfigType[cArg] - aValue = cfg[k] - aType = type(aValue).__name__ - if aType != aTypeCheck: - print(f"ERRR:LoadConfig:{k}:expected type [{aTypeCheck}] got type [{aType}]") - exit(112) - gMe[cArg] = aValue - except KeyError: - print(f"ERRR:LoadConfig:{k}:UnknownCommand") - exit(113) - - -def process_args(args: list[str]): - """ - Helper to process command line arguments. - - Flow setup below such that - * location of --config in commandline will decide whether command line or config file will get - priority wrt setting program parameters. - * str type values in cmdline are picked up directly, without running them through ast.literal_eval, - bcas otherwise one will have to ensure throught the cmdline arg mechanism that string quote is - retained for literal_eval - """ - import ast - import json - global gMe - iArg = 1 - while iArg < len(args): - cArg = args[iArg] - if (not cArg.startswith("--")): - print(f"ERRR:ProcessArgs:{iArg}:{cArg}:MalformedCommandOr???") - exit(101) - print(f"DBUG:ProcessArgs:{iArg}:{cArg}") - try: - aTypeCheck = gConfigType[cArg] - aValue = args[iArg+1] - if aTypeCheck != 'str': - aValue = ast.literal_eval(aValue) - aType = type(aValue).__name__ - if aType != aTypeCheck: - print(f"ERRR:ProcessArgs:{iArg}:{cArg}:expected type [{aTypeCheck}] got type [{aType}]") - exit(102) - gMe[cArg] = aValue - iArg += 2 - if cArg == '--config': - load_config() - except KeyError: - print(f"ERRR:ProcessArgs:{iArg}:{cArg}:UnknownCommand") - exit(103) - print(json.dumps(gMe, indent=4)) - for k in gConfigNeeded: - if gMe.get(k) == None: - print(f"ERRR:ProcessArgs:{k}:missing, did you forget to pass the config file...") - exit(104) - mDebug.setup(gMe['--debug']) - uv.validator_setup(gMe['--allowed.schemes'], gMe['--allowed.domains']) - - def setup_server(): """ Helps setup a http/https server @@ -333,5 +236,5 @@ def run(): if __name__ == "__main__": - process_args(sys.argv) + gMe.process_args(sys.argv) run()