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