diff --git a/bin/qcms b/bin/qcms index 0ab1ff6..531ef9d 100755 --- a/bin/qcms +++ b/bin/qcms @@ -19,6 +19,7 @@ import base64 import io import cms from cms import index +import cms.cli from cms.engine.config.structure import Layout, System from cms.engine import project, themes @@ -142,8 +143,7 @@ def set_cloud(manifest:Annotated[str,typer.Argument(help="path of the auth-file else: _msg = INVALID_FOLDER print (_msg) -@cli.command(name='secure') -# def set_key(path): +@cli.command(name='set-key') def secure( manifest:Annotated[str,typer.Argument(help="path of the manifest file")], keyfile:Annotated[str,typer.Argument(help="path of the key file to generate")]): @@ -202,77 +202,77 @@ def load(**_args): return getattr(module,_name) if hasattr(module,_name) else None -@cli.command(name='plugins') -def plugin_manager (manifest:Annotated[str,typer.Argument(help="path to manifest file")], - show:bool=typer.Option(default=False,help="list plugins loaded"), - add: Annotated[Optional[bool],typer.Option("--register/--unregister",help="add/remove a plugin to manifest use with --pointer option")] = None, - pointer:str=typer.Option(default=None,help="pointer is structured as 'filename.function'") - ) : - """ - Manage plugins list loaded plugins, - """ - manifest = get_manifest(manifest) - _config = cms.engine.config.get(manifest) - if _config : - _root = os.sep.join(manifest.split(os.sep)[:-1] + [_config['layout']['root'],'_plugins']) - else : - _root = None - if _root and os.path.exists(_root) : - # files = os.listdir(_root) - _msg = f"""{FAILED} no operation was specified, please use --help option""" - # if 'plugins' in _config : - _plugins = _config['plugins'] if 'plugins' in _config else {} +# @cli.command(name='plugins') +# def plugin_manager (manifest:Annotated[str,typer.Argument(help="path to manifest file")], +# show:bool=typer.Option(default=False,help="list plugins loaded"), +# add: Annotated[Optional[bool],typer.Option("--register/--unregister",help="add/remove a plugin to manifest use with --pointer option")] = None, +# pointer:str=typer.Option(default=None,help="pointer is structured as 'filename.function'") +# ) : +# """ +# Manage plugins list loaded plugins, +# """ +# manifest = get_manifest(manifest) +# _config = cms.engine.config.get(manifest) +# if _config : +# _root = os.sep.join(manifest.split(os.sep)[:-1] + [_config['layout']['root'],'_plugins']) +# else : +# _root = None +# if _root and os.path.exists(_root) : +# # files = os.listdir(_root) +# _msg = f"""{FAILED} no operation was specified, please use --help option""" +# # if 'plugins' in _config : +# _plugins = _config['plugins'] if 'plugins' in _config else {} - if show : - if not _plugins : - _msg = f"""{FAILED} no plugins are loaded\n\t{manifest}""" - else: - _data = [] - _plugConf = _config['plugins'] +# if show : +# if not _plugins : +# _msg = f"""{FAILED} no plugins are loaded\n\t{manifest}""" +# else: +# _data = [] +# _plugConf = _config['plugins'] - for _name in _plugConf : - _log = {"files":_name,"loaded":len(_plugConf[_name]),"functions":json.dumps(_plugConf[_name])} - _data.append(_log) - _data= pd.DataFrame(_data) +# for _name in _plugConf : +# _log = {"files":_name,"loaded":len(_plugConf[_name]),"functions":json.dumps(_plugConf[_name])} +# _data.append(_log) +# _data= pd.DataFrame(_data) - # # # _data = plugins.stats(_plugins) - # # _data = cms.Plugin.stats(_plugins) - print (to_Table(_data)) - _msg = f"""{PASSED} [bold]{_config['layout']['header']['title']}[/bold]: found a total of {_data.loaded.sum()} plugins loaded from {_data.shape[0]} file(s)\n\t{_root}""" +# # # # _data = plugins.stats(_plugins) +# # # _data = cms.Plugin.stats(_plugins) +# print (to_Table(_data)) +# _msg = f"""{PASSED} [bold]{_config['layout']['header']['title']}[/bold]: found a total of {_data.loaded.sum()} plugins loaded from {_data.shape[0]} file(s)\n\t{_root}""" - if add in [True,False] and pointer : +# if add in [True,False] and pointer : - file,fnName = pointer.split('.') - # _fnpointer = plugins.load(_root,file+'.py',fnName) - # _fnpointer = cms.Plugin.load(_root,file+'.py',fnName) - _ploader = plugin_ix.Loader(file = os.sep.join([_root,file+'.py'])) +# file,fnName = pointer.split('.') +# # _fnpointer = plugins.load(_root,file+'.py',fnName) +# # _fnpointer = cms.Plugin.load(_root,file+'.py',fnName) +# _ploader = plugin_ix.Loader(file = os.sep.join([_root,file+'.py'])) - if add and _ploader.has(fnName): - if file not in _plugins : - _plugins[file] = [] +# if add and _ploader.has(fnName): +# if file not in _plugins : +# _plugins[file] = [] - if fnName not in _plugins[file] : - _plugins[file].append(fnName) - _msg = f"""{PASSED} [bold]{_config['layout']['header']['title']}[/bold]: registered {pointer}, use the --show option to list loaded plugins""" - else: - _msg = f"""{FAILED} [bold]{_config['layout']['header']['title']}[/bold]: could not register {pointer}, it already exists""" - elif add is False and file in _plugins: - _plugins[file] = [_name.strip() for _name in _plugins[file] if _name.strip() != fnName.strip() ] - _msg = f"""{PASSED} [bold]{_config['layout']['header']['title']}[/bold]: unregistered {pointer}, use the --show option to list loaded plugins """ +# if fnName not in _plugins[file] : +# _plugins[file].append(fnName) +# _msg = f"""{PASSED} [bold]{_config['layout']['header']['title']}[/bold]: registered {pointer}, use the --show option to list loaded plugins""" +# else: +# _msg = f"""{FAILED} [bold]{_config['layout']['header']['title']}[/bold]: could not register {pointer}, it already exists""" +# elif add is False and file in _plugins: +# _plugins[file] = [_name.strip() for _name in _plugins[file] if _name.strip() != fnName.strip() ] +# _msg = f"""{PASSED} [bold]{_config['layout']['header']['title']}[/bold]: unregistered {pointer}, use the --show option to list loaded plugins """ - # # - # # We need to write this down !! - if add in [True,False] : +# # # +# # # We need to write this down !! +# if add in [True,False] : - _config['plugins'] = _plugins - cms.engine.config.write(_config,manifest) - # else: - # _msg = f"""{FAILED} [bold]{_config['layout']['header']['title']}[/bold]: no plugins are loaded """ - print() - print(_msg) - else: - _msg = f"""{FAILED} No plugin folder could be found in {manifest}""" - print (_msg) +# _config['plugins'] = _plugins +# cms.engine.config.write(_config,manifest) +# # else: +# # _msg = f"""{FAILED} [bold]{_config['layout']['header']['title']}[/bold]: no plugins are loaded """ +# print() +# print(_msg) +# else: +# _msg = f"""{FAILED} No plugin folder could be found in {manifest}""" +# print (_msg) @cli.command (name='create') @@ -501,4 +501,6 @@ def handle_theme ( global SYS_ARGS if __name__ == '__main__': cli.add_typer(cli_theme,name="themes",help="manage themes associated with a site") + cli.add_typer(cms.cli.auth.cli,name="login",help="manage login (authentication/authorization) to a sites") + cli.add_typer(cms.cli.plugins.cli,name="plugins",help="manage plugins associated with a site") cli() diff --git a/cms/__init__.py b/cms/__init__.py index 18e5bab..5587859 100644 --- a/cms/__init__.py +++ b/cms/__init__.py @@ -10,6 +10,14 @@ from . import sites from . import apexchart from . import meta from . import secure + +# def get_manifest (manifest): +# if not manifest.endswith('json') and os.path.isdir(manifest): +# manifest = manifest if not manifest.endswith(os.sep) else os.sep.join(manifest.split(os.sep)[:-1]) +# return os.sep.join([manifest,'qcms-manifest.json']) +# else: +# return manifest + class Plugin : # # decorator for plugin functions, this is a preliminary to enable future developement @@ -87,8 +95,7 @@ class Plugin : -# -# default plugins to load into the configuration file + @Plugin(mimetype="application/json") def authorizationURL (**_args): # _config = _args['config'] @@ -144,4 +151,4 @@ def oauthFinalize (**_args):
Please wait ...
""" - return _html \ No newline at end of file + return _html diff --git a/cms/cli/__init__.py b/cms/cli/__init__.py new file mode 100644 index 0000000..6b1f4df --- /dev/null +++ b/cms/cli/__init__.py @@ -0,0 +1 @@ +from . import plugins, secure, auth \ No newline at end of file diff --git a/cms/cli/auth.py b/cms/cli/auth.py new file mode 100644 index 0000000..ccc0d6d --- /dev/null +++ b/cms/cli/auth.py @@ -0,0 +1,85 @@ +import typer +from typing_extensions import Annotated +from typing import Optional +from typing import Tuple +import os +import json +import plugin_ix as px +from enum import Enum +import pandas as pd +from rich.table import Table +from rich import print +import uuid +import cms +import requests + + +FAILED = '[ [red] \u2717 [/red] ]' +PASSED = '[ [green] \u2713 [/green] ]' +cli = typer.Typer() +@cli.command(name="set") +def set_auth ( + id:Annotated[str,typer.Argument(help="identifier used in setting cookies")], + manifest:Annotated[str,typer.Argument(help="path manifest or site folder")], + registry:Annotated[str,typer.Argument(help="path to the registry created by plugin-ix")], + authpath:Annotated[str,typer.Argument(help="Authentication file location, that contains the authentication model")] + ): + """ + This function will set login configuration to a manifest + """ + try: + _args = {"id":id,"registry":registry,"path":authpath,"authorization":{}} + path = cms.engine.config.get_manifest(manifest=manifest) + _config = cms.engine.config.get(path) + _config['system']['source'] = {'secure':_args} + _secEngine = cms.secure.Manager(config=_config) + cms.engine.config.write(config=_config,path=path) + except Exception as e: + print (f"""{FAILED} [bold][red]{id}, failed [/red][/bold], error found {str(e)}""") + # + # we should try to test this configuration to see if it works + # + + pass +def permissions(): + pass +def inspect (): + pass +@cli.command("drop") +def remove (manifest:Annotated[str,typer.Argument(help="path manifest or site folder")]): + """ + This function removes login configuration from a manifest + """ + path = cms.engine.config.get_manifest(manifest=manifest) + _config = cms.engine.config.get(path) + _msg = f'{FAILED}' + try: + if 'secure' in _config['system']['source'] : + del _config['system']['source']['secure'] + cms.engine.config.write(config=_config,path=path) + _msg = f'{PASSED} [green]Successfully[/green] removed secure attribute' + except Exception as e: + _msg = f'{_msg}, error found {str(e)}' + + print (_msg) + + pass +@cli.command(name="template") +def gen_auth ( + _model:Annotated[str,typer.Argument(help="generate an authentication parameter file")], +): + """ + This function generates a template login file content for a model (pam, oauth2, nextcloud) + """ + _conf = {"method":_model} + _map = {"pam":{"age":3600},"nextcloud":{"url":"https://your-nextcloud-site"}} + # + # parameters client_id, client-secret, + _map["oauth2"] = {"client_id":"client_id","client_secret":"client_secret","scope":"profile","response_type":"code","authorization_url":"authorization_url","redirect_uri":"redirect_uri"} + if _model in _map : + _conf = dict(_conf, **_map.get(_model)) + print (_conf) + print () + print(f'{PASSED} [bold] Edit[/bold] and [bold]save[/bold] the content into an [bold]"authentication file"[/bold]') + else: + print (f"""{FAILED} [bold]{_model}[/bold] is not a supported authentication model""") \ No newline at end of file diff --git a/cms/cli/plugins.py b/cms/cli/plugins.py new file mode 100644 index 0000000..73c2cf6 --- /dev/null +++ b/cms/cli/plugins.py @@ -0,0 +1,207 @@ +import typer +from typing_extensions import Annotated +from typing import Optional +from typing import Tuple +import os +import json +import plugin_ix as px +from enum import Enum +import pandas as pd +from rich.table import Table +from rich import print +import cms + + +FAILED = '[ [red] \u2717 [/red] ]' +PASSED = '[ [green] \u2713 [/green] ]' + +""" +Handling cli interface for plugins, performing add list and remove +""" + +cli = typer.Typer() +def to_Table(df: pd.DataFrame): + """Displays a Pandas DataFrame as a rich table.""" + table = Table(show_header=True, header_style="bold magenta") + + for col in df.columns: + table.add_column(col) + + for _, row in df.iterrows(): + table.add_row(*row.astype(str).tolist()) + + # console.print(table) + return table + + +# def cms.get_manifest (manifest): +# if not manifest.endswith('json') and os.path.isdir(manifest): +# manifest = manifest if not manifest.endswith(os.sep) else os.sep.join(manifest.split(os.sep)[:-1]) +# path = os.sep.join([manifest,'qcms-manifest.json']) +# else: +# path = manifest +# return path + +# def get_config(path,key=None): + +# f = open(path) +# _config = json.loads(f.read()) +# f.close() +# return _config[key] if key and key in _config else _config + +def format(file,_content): + return [{'file':file.replace('.py',''),'uri':f'api/{file.replace(".py","")}/{_name}','endpoint':_name} for _name in _content] + +def get (manifest:Annotated[str,typer.Argument(help="project folder or manifest file")],): + """ + List the plugins in the from a project folder (from the manifest) + """ + path = cms.engine.config.get_manifest(manifest=manifest) # + _config = cms.engine.config.get(path) #get_config(path) + + + _root = _config['layout']['root'] + # + # remove the manifest file to pull the plugins folder + folder = os.sep.join([path.replace('qcms-manifest.json','')[:-1],f'{_root}/_plugins']) + loader = px.Loader () + + _api = [] + for _name in os.listdir(folder): + + _file = os.sep.join([folder,_name]) + try: + loader.load(file=_file) + _api+= format(_name,loader.names()) + # _ondisk[_name] = loader.names() + + + except Exception as e: + # + # If the file has an error, it will be skipped + print (e) + break + # + # at this point we know what we have on disk, we should be able to make a report/dashboard of sorts + pass + + # On disk + # files = os.listdir(folder) + # + _online = [] + _plugins = _config['plugins'] + for _name in _plugins : + _online += format(_name, _plugins[_name]) + return _api,_online + +class StatusFilter(str,Enum): + online="online" + offline="offline" + + +@cli.command(name="status") +def status(manifest:Annotated[str,typer.Argument(help="project folder or manifest file")], + filter : StatusFilter = None + ): + """ + This function will provide the status of all available plugins + """ + _avail,_depl = get(manifest) + _avail = pd.DataFrame(_avail)# available on disk (not deployed) + _depl = pd.DataFrame(_depl) # deployed + files = _avail.file.unique().tolist()+ _depl.file.unique().tolist() + _df = pd.DataFrame() + for _name in files : + _xe = _avail[_avail.file == _name].endpoint.tolist() + _ye = _depl[_depl.file == _name].endpoint.tolist() + _online = list(set(_xe) & set(_ye)) + _offline = list(set(_xe) - set(_ye)) + _data = pd.DataFrame() + + + _data['api'] = _offline + _online + _data['file'] = _name + _data['status'] = (['[red]offline[/red]']* len(_offline)) + (['[green]online[/green]']* len(_online)) + # _data['deployed'] = _online + _data = _data[['file','api','status']] + _df = pd.concat([_df, _data]) + # _df.append({'file':_name,'deployed':_online,'offline':_offline}) + _df['uri'] = _df.apply(lambda row: f'api/{row.file}/{row.api}',axis=1) + _df = _df[['file','uri','api','status']] + if filter : + print (f"status in ('{filter.value}')") + _df = _df[_df.status.str.contains(filter.value, case=False, regex=True)] + # _df = _df.query(f" '{filter.value}' in status") + print (to_Table(_df)) + pass + +@cli.command(name="register") +def add (manifest:Annotated[str,typer.Argument(help="project folder or manifest file")], + pointer:Annotated[str,typer.Argument(help="file/function or file.function with no file extension. e.g: demo/info")] + ): + """ + This function will add a plugin function to the site's configuration file making it available as an API + """ + # + # Let's make sure we are adding to the configuration something that actually exists + # + _file,_name = pointer.split('/') + path = cms.engine.config.get_manifest(manifest=manifest) # + _config = cms.engine.config.get(path) #get_config(path) + _root = _config['layout']['root'] + + _folder = os.sep.join([path.replace('qcms-manifest.json','')[:-1],f'{_root}/_plugins']) + if f'{_file}.py' in os.listdir(_folder) : + loader = px.Loader () + loader.load(file=f'{_folder}{os.sep}{_file}.py') + if loader.has(_name) : + # + # adding to plugins + _plugins = _config['plugins'] + if _file not in _plugins : + _plugins[_file] = [] + if _name not in _plugins[_file] : + _plugins[_file].append (_name) + _config['plugins'] = _plugins + cms.engine.config.write(config=_config,path=path) + _msg = f"{PASSED} [bold]{pointer}[/bold] successfully added to {manifest}\nAPI endpoint [bold]api/{_file}/{_name}[/bold]" + else: + _msg = f"{FAILED} [bold]{pointer}[/bold] [red]already exists[/red] in configuration" + else: + # + # failure at this point, asking for a function in a file that doesn't exist + _msg = f"[bold]{pointer}[/bold] [red]{_name} missing[/red] in {_folder}{os.sep}{_file}.py" + pass + else: + # + # throw/raise an exception + _msg = f"{FAILED} [bold]{pointer}[/bold] [red]NOT found[/red] in {_folder}" + + print (_msg) + pass +@cli.command(name="unregister") +def remove(manifest:Annotated[str,typer.Argument(help="project folder or manifest file")], + pointer:Annotated[str,typer.Argument(help="file/function or file.function with no file extension. e.g: demo/info")] + ): + """ + This function will remove an api from the configuration of a project and won't be available via http/https + """ + _file,_name = pointer.split('/') + path = cms.engine.config.get_manifest(manifest=manifest) # + _config = cms.engine.config.get(path) #get_config(path) + _root = _config['layout']['root'] + _plugins = _config.get('plugins',{}) + if _file in _plugins : + _plugins[_file] = [_fname for _fname in _plugins[_file] if _fname != _name] + _config['plugins'] = _plugins + _msg = f"{PASSED} [bold]{pointer}[/bold] was [green]successfully[/green] removed from {path}" + else: + _msg = f"{FAILED} [bold]{pointer}[/bold] was [red]NOT found[/red] in {path}" + print (_msg) + cms.engine.config.write(_config,path) + # + # +# def post(config,path): +# f = open(path,'w') +# f.write(json.dumps(config,indent=4)) +# f.close() \ No newline at end of file diff --git a/cms/cli/secure.py b/cms/cli/secure.py new file mode 100644 index 0000000..144ec61 --- /dev/null +++ b/cms/cli/secure.py @@ -0,0 +1,88 @@ +""" +This file will handle security aspects associated with QCMS +""" + +import typer +from typing_extensions import Annotated +from typing import Optional +from typing import Tuple +import os +import json +import plugin_ix as px +from enum import Enum +import pandas as pd +from rich.table import Table +from rich import print +import uuid +import cms +import requests + + +FAILED = '[ [red] \u2717 [/red] ]' +PASSED = '[ [green] \u2713 [/green] ]' +cli = typer.Typer() + + +@cli.command(name="set-key") +def set_key (manifest:Annotated[str,typer.Argument(help="path to manifest or manifest folder")], + keyfile:Annotated[str,typer.Argument(help="path of the key file to generate")] + ): + """ + force-reload of an application + """ + keyfile = cms.engine.config.get_manifest(keyfile) + if not os.path.exists(keyfile): + f = open(keyfile,'w') + f.write(str(uuid.uuid4())) + f.close() + # + manifest = cms.engine.config.get_manifest(manifest) + _config = cms.engine.config.get(manifest) + if 'source' not in _config['system']: + _config['system']['source'] = {'id':'disk'} + _config['system']['source']['key'] = os.path.abspath(keyfile) + cms.engine.config.write(_config,manifest) + _msg = f"""{PASSED} [bold]{_config['layout']['header']['title']}[/bold] : A key was generated and written to {keyfile} + use this key in header to enable reload of the site ... + """ + else: + _msg = f"""{FAILED} [bold]{_config['system']['layout']['header']['title']}[/bold] : could [bold]NOT[/bold] generate a key, because it would seem you already have one + Please manually delete {keyfile} + + + """ + print (_msg) +@cli.command (name='reload') +def reload ( + path:Annotated[str,typer.Argument(help="")], + port:int=typer.Option(default=None,help="port of the host to call") + ) : + """ + Reload a site/portal given the manifest ... + """ + path = cms.engine.config.get_manifest(path) + _config = cms.engine.config.get( path) + if 'source' in _config['system'] and 'key' in _config['system']['source'] : + _spath = _config['system']['source']['key'] + # f = open(_config['system']['source']['key']) + if not os.path.exists(_spath) : + mpath = path.split(os.sep)[:-1] + _spath.split(os.sep) + _spath = os.sep.join(mpath) + pass + + f = open(_spath) + key = f.read() + f.close() + + _port = port if port else _config['system']['app']['port'] + url = f"http://localhost:{_port}/reload" + resp = requests.post(url, headers={"key":key}) + if resp.status_code == 200 : + _msg = f"""{PASSED} [bold]{_config['layout']['header']['title']}[/bold] : successfully reloaded {url}""" + else: + _msg = f"""{FAILED} failed to reload, status code {resp.status_code}\n{url} + """ + else: + _msg = f"""{FAILED} no secure key found in manifest to request reload""" + print (_msg) + diff --git a/cms/engine/config/__init__.py b/cms/engine/config/__init__.py index 8de73a0..83a8c41 100644 --- a/cms/engine/config/__init__.py +++ b/cms/engine/config/__init__.py @@ -5,6 +5,8 @@ import os import json def get (path) : + # + # Opens the manifest file and returns the configuration if os.path.exists(path) : f = open(path) _conf = json.loads(f.read()) @@ -12,6 +14,15 @@ def get (path) : else: _conf = {} return _conf +def get_manifest(manifest): + # + # returns the manifest file (absolute path) for a site + if not manifest.endswith('json') and os.path.isdir(manifest): + manifest = manifest if not manifest.endswith(os.sep) else os.sep.join(manifest.split(os.sep)[:-1]) + return os.sep.join([manifest,'qcms-manifest.json']) + else: + return manifest + def _isvalid(_allowed,**_args): if not list(set(_allowed) - set(_args.keys())) : @@ -21,8 +32,8 @@ def _isvalid(_allowed,**_args): return _pargs return False -def write(_config, path): +def write(config, path): f = open(path,'w') - f.write( json.dumps(_config,indent=2)) ; + f.write( json.dumps(config,indent=4)) ; f.close() diff --git a/cms/engine/project/__init__.py b/cms/engine/project/__init__.py index 65fe2e9..458cdaf 100644 --- a/cms/engine/project/__init__.py +++ b/cms/engine/project/__init__.py @@ -60,12 +60,11 @@ import cms # register this in config.plugins: {"demo":["info"]} -@cms.plugins(mimetype='application/json') : +@cms.Plugin(mimetype='application/json',methods=['POST','GET']) def info (**_args): _request= _args['request'] _config = _args['config'] return {"version":_config['system']['version'],'title':_config['layout']['header']['title']} - pass """ loc = os.sep.join([_path,_root,'_plugins','demo.py']) f = open(loc,'w') @@ -137,6 +136,9 @@ def _index (_path,root):

+

+ Add custom python functions and provide access as api +
Learn more about QCMS and at {themes.URL}

""" diff --git a/cms/secure.py b/cms/secure.py index 416b20b..33aa12c 100644 --- a/cms/secure.py +++ b/cms/secure.py @@ -63,9 +63,10 @@ class Manager : # we will assume that all authenticated users have access to every part of the site # if 'authorization' in _kwargs: - # - # loading permissions table from a designated location + # # + # # loading permissions table from a designated location reader = transport.get.reader(**_kwargs['authorization']) + self._permissions = reader.read() @@ -77,7 +78,6 @@ class Manager : if not path or not os.path.exists(path) : raise Exception (f'Missing {what} {path}') def authenticate(self,**_args): - print (" ********* ", self._authKey) _kwargs = copy.copy(_args) _kwargs['config'] = self._config # _user = self.login(**_kwargs) @@ -147,7 +147,7 @@ class Manager : return None def method (self): - return self._config['method'] + return self._config['method'] if self._config else None # def _uri (self) : # return self._config.get('uri',None) def loginURI (self): diff --git a/cms/sites/__init__.py b/cms/sites/__init__.py index 3ae6618..28553d4 100644 --- a/cms/sites/__init__.py +++ b/cms/sites/__init__.py @@ -110,7 +110,7 @@ class Initialization (IOConfig): # # Log initializaton ... # - self.log(action='init.security',module='site.init',input= self.secure._permissions.to_dict(orient='records')) + self.log(action='init.security',module='site.init',input= {'method':self.secure.method(),'permissioins':self.secure._permissions.to_dict(orient='records')}) def reload (self): _args = self._args self._config = self.read_config(**_args) @@ -428,10 +428,10 @@ class Site(Initialization) : def html (self,_request): _uri = self.uri(_request) _mimeType = self.mimeType(_uri) - f = open(self.path(_uri),'r') - _content = f.read() #_handler.html(_uri, self.get(None)) - f.close() - + # f = open(self.path(_uri),'r') + # _content = f.read() #_handler.html(_uri, self.get(None)) + # f.close() + _content = self.open(uri=self.path(_uri),mode='r') if 'md' in _mimeType or 'html' in _mimeType : # _content = f'
{_content}
' @@ -468,7 +468,22 @@ class Site(Initialization) : # _isfile = '.' in request.path # return file if _isfile and not _isroute else self.get('layout.index') - + def open(self,**_args) : + """ + :uri path of the file to open + :mode r,rb {text,binary} + """ + _mode = 'r' if 'mode' not in _args else _args['mode'] + _uri = _args['uri'] + + if _uri.endswith('.py') and '_plugins' in _uri and self.get('layout.root') not in _uri: + # + # We can NOT serve python files over the web (possible security issue) + return None + f = open(_uri,_mode) + _content = f.read() + f.close() + return _content def read(self,request) : _kwargs = {'allow':0} # if self.secure.allow(request=request): @@ -484,11 +499,12 @@ class Site(Initialization) : # # Opening a binary file - f = open(self.path(_uri),'rb') - _content = io.BytesIO(f.read()) + # f = open(self.path(_uri),'rb') + # _content = io.BytesIO(f.read()) - f.close() - # _content,_ = _handler.read(uri=_uri, config=self.get(None)) + # f.close() + _content = self.open(uri=self.path(_uri),mode='rb') + _content = io.BytesIO( _content ) _kwargs = {'allow':1,'mimeType':_mimeType,'extension':_extension, 'path':self.path(_uri), 'uri':_uri,'request':request.path} diff --git a/cms/static/js/qcms/qcms-login.js b/cms/static/js/qcms/qcms-login.js index 25ea4d9..d1cc600 100644 --- a/cms/static/js/qcms/qcms-login.js +++ b/cms/static/js/qcms/qcms-login.js @@ -45,43 +45,4 @@ qcms.login.cancel = function (){ }) $('.qcms-login-error').slideUp() } -/** - * PAM authentication - */ -// qcms.authenticate = {} -// qcms.authenticate.nextcloud = function (){ -// var _uri = ([qcms.context,'/login']).join('') -// _args = qcms.login.get() - -// var http = HttpClient.instance() -// http.setHeader('Content-Type','application/json') -// // http.setHeader('method','pam') -// http.setData( JSON.stringify(_args)) -// http.post(_uri,(x)=>{ -// if(x.readyState == 4 && x.status == 200){ - -// } -// }) - -// } -// qcms.authenticate.pam = function (){ -// var _uri = ([qcms.context,'/login']).join('') -// var _args = { -// username:$('.qcms-login-input .username').val(), -// password:$('.qcms-login-input .password').val() -// } - -// var http = HttpClient.instance() -// http.setHeader('Content-Type','application/json') -// http.setHeader('method','pam') -// http.setData( JSON.stringify(_args)) -// http.post(_uri,(x)=>{ - -// if(x.readyState == 4 && x.status == 200){ - -// window.open(x.responseURL,'_self') -// ; -// } -// }) -// } \ No newline at end of file