| 
									
										
										
										
											2020-08-13 17:48:26 +00:00
										 |  |  | #!/usr/bin/python3 | 
					
						
							| 
									
										
										
										
											2021-03-18 12:02:16 +00:00
										 |  |  | from __future__ import annotations  # TODO: Remove if python requirement is >= 3.10 | 
					
						
							| 
									
										
										
										
											2020-10-18 23:42:54 +00:00
										 |  |  | import inspect | 
					
						
							| 
									
										
										
										
											2020-09-03 15:56:42 +00:00
										 |  |  | import argparse | 
					
						
							| 
									
										
										
										
											2020-11-11 22:56:07 +00:00
										 |  |  | import sys | 
					
						
							| 
									
										
										
										
											2021-03-18 12:02:16 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-02 03:38:38 +00:00
										 |  |  | import pkg_resources | 
					
						
							| 
									
										
										
										
											2021-11-28 21:23:34 +00:00
										 |  |  | from sqlalchemy import exc | 
					
						
							| 
									
										
										
										
											2020-11-18 00:55:02 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-11-28 21:23:34 +00:00
										 |  |  | from flaschengeist.app import create_app, install_all | 
					
						
							| 
									
										
										
										
											2020-11-18 00:55:02 +00:00
										 |  |  | from flaschengeist.config import config | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class PrefixMiddleware(object): | 
					
						
							| 
									
										
										
										
											2021-03-18 16:27:19 +00:00
										 |  |  |     def __init__(self, app, prefix=""): | 
					
						
							| 
									
										
										
										
											2020-11-18 00:55:02 +00:00
										 |  |  |         self.app = app | 
					
						
							|  |  |  |         self.prefix = prefix | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def __call__(self, environ, start_response): | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-03-18 16:27:19 +00:00
										 |  |  |         if environ["PATH_INFO"].startswith(self.prefix): | 
					
						
							| 
									
										
										
										
											2021-05-26 14:47:03 +00:00
										 |  |  |             environ["PATH_INFO"] = environ["PATH_INFO"][len(self.prefix) :] | 
					
						
							| 
									
										
										
										
											2021-03-18 16:27:19 +00:00
										 |  |  |             environ["SCRIPT_NAME"] = self.prefix | 
					
						
							| 
									
										
										
										
											2020-11-18 00:55:02 +00:00
										 |  |  |             return self.app(environ, start_response) | 
					
						
							|  |  |  |         else: | 
					
						
							| 
									
										
										
										
											2021-03-18 16:27:19 +00:00
										 |  |  |             start_response("404", [("Content-Type", "text/plain")]) | 
					
						
							| 
									
										
										
										
											2020-11-18 00:55:02 +00:00
										 |  |  |             return ["This url does not belong to the app.".encode()] | 
					
						
							| 
									
										
										
										
											2020-11-02 03:38:38 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class InterfaceGenerator: | 
					
						
							|  |  |  |     known = [] | 
					
						
							|  |  |  |     classes = {} | 
					
						
							| 
									
										
										
										
											2021-03-18 16:27:19 +00:00
										 |  |  |     mapper = { | 
					
						
							|  |  |  |         "str": "string", | 
					
						
							|  |  |  |         "int": "number", | 
					
						
							|  |  |  |         "float": "number", | 
					
						
							|  |  |  |         "date": "Date", | 
					
						
							|  |  |  |         "datetime": "Date", | 
					
						
							|  |  |  |         "NoneType": "null", | 
					
						
							|  |  |  |         "bool": "boolean", | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2020-11-02 03:38:38 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |     def __init__(self, namespace, filename): | 
					
						
							|  |  |  |         self.basename = "" | 
					
						
							|  |  |  |         self.namespace = namespace | 
					
						
							|  |  |  |         self.filename = filename | 
					
						
							| 
									
										
										
										
											2020-11-11 22:56:07 +00:00
										 |  |  |         self.this_type = None | 
					
						
							| 
									
										
										
										
											2020-11-02 03:38:38 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |     def pytype(self, cls): | 
					
						
							| 
									
										
										
										
											2021-03-18 12:02:16 +00:00
										 |  |  |         a = self._pytype(cls) | 
					
						
							|  |  |  |         print(f"{cls} -> {a}") | 
					
						
							|  |  |  |         return a | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def _pytype(self, cls): | 
					
						
							|  |  |  |         import typing | 
					
						
							| 
									
										
										
										
											2021-03-18 16:27:19 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-03-18 12:02:16 +00:00
										 |  |  |         origin = typing.get_origin(cls) | 
					
						
							|  |  |  |         arguments = typing.get_args(cls) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if origin is typing.ForwardRef:  # isinstance(cls, typing.ForwardRef): | 
					
						
							|  |  |  |             return "", "this" if cls.__forward_arg__ == self.this_type else cls.__forward_arg__ | 
					
						
							|  |  |  |         if origin is typing.Union: | 
					
						
							|  |  |  |             print(f"A1:  {arguments[1]}") | 
					
						
							|  |  |  |             if len(arguments) == 2 and arguments[1] is type(None): | 
					
						
							|  |  |  |                 return "?", self.pytype(arguments[0])[1] | 
					
						
							| 
									
										
										
										
											2020-11-02 03:38:38 +00:00
										 |  |  |             else: | 
					
						
							| 
									
										
										
										
											2021-03-18 12:02:16 +00:00
										 |  |  |                 return "", "|".join([self.pytype(pt)[1] for pt in arguments]) | 
					
						
							|  |  |  |         if origin is list: | 
					
						
							|  |  |  |             return "", "Array<{}>".format("|".join([self.pytype(a_type)[1] for a_type in arguments])) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         name = cls.__name__ if hasattr(cls, "__name__") else cls if isinstance(cls, str) else None | 
					
						
							|  |  |  |         if name is not None: | 
					
						
							|  |  |  |             if name in self.mapper: | 
					
						
							|  |  |  |                 return "", self.mapper[name] | 
					
						
							|  |  |  |             else: | 
					
						
							|  |  |  |                 return "", name | 
					
						
							| 
									
										
										
										
											2020-11-11 22:56:07 +00:00
										 |  |  |         print( | 
					
						
							| 
									
										
										
										
											2021-03-18 12:02:16 +00:00
										 |  |  |             "WARNING: This python version might not detect all types (try >= 3.9). Could not identify >{}<".format(cls) | 
					
						
							| 
									
										
										
										
											2020-11-11 22:56:07 +00:00
										 |  |  |         ) | 
					
						
							| 
									
										
										
										
											2020-11-02 03:38:38 +00:00
										 |  |  |         return "?", "any" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def walker(self, module): | 
					
						
							| 
									
										
										
										
											2021-03-18 12:02:16 +00:00
										 |  |  |         if sys.version_info < (3, 9): | 
					
						
							|  |  |  |             raise RuntimeError("Python >= 3.9 is required to export API") | 
					
						
							|  |  |  |         import typing | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-11 22:56:07 +00:00
										 |  |  |         if ( | 
					
						
							| 
									
										
										
										
											2021-05-26 14:47:03 +00:00
										 |  |  |             inspect.ismodule(module[1]) | 
					
						
							|  |  |  |             and module[1].__name__.startswith(self.basename) | 
					
						
							|  |  |  |             and module[1].__name__ not in self.known | 
					
						
							| 
									
										
										
										
											2020-11-11 22:56:07 +00:00
										 |  |  |         ): | 
					
						
							| 
									
										
										
										
											2020-11-02 03:38:38 +00:00
										 |  |  |             self.known.append(module[1].__name__) | 
					
						
							|  |  |  |             for cls in inspect.getmembers(module[1], lambda x: inspect.isclass(x) or inspect.ismodule(x)): | 
					
						
							|  |  |  |                 self.walker(cls) | 
					
						
							|  |  |  |         elif ( | 
					
						
							| 
									
										
										
										
											2021-05-26 14:47:03 +00:00
										 |  |  |             inspect.isclass(module[1]) | 
					
						
							|  |  |  |             and module[1].__module__.startswith(self.basename) | 
					
						
							|  |  |  |             and module[0] not in self.classes | 
					
						
							|  |  |  |             and not module[0].startswith("_") | 
					
						
							|  |  |  |             and hasattr(module[1], "__annotations__") | 
					
						
							| 
									
										
										
										
											2020-11-02 03:38:38 +00:00
										 |  |  |         ): | 
					
						
							| 
									
										
										
										
											2020-11-11 22:56:07 +00:00
										 |  |  |             self.this_type = module[0] | 
					
						
							| 
									
										
										
										
											2021-03-18 12:02:16 +00:00
										 |  |  |             print("\n\n" + module[0] + "\n") | 
					
						
							|  |  |  |             d = {} | 
					
						
							|  |  |  |             for param, ptype in typing.get_type_hints(module[1], globalns=None, localns=None).items(): | 
					
						
							|  |  |  |                 if not param.startswith("_") and not param.endswith("_"): | 
					
						
							|  |  |  |                     print(f"{param} ::: {ptype}") | 
					
						
							|  |  |  |                     d[param] = self.pytype(ptype) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-02 03:38:38 +00:00
										 |  |  |             if len(d) == 1: | 
					
						
							|  |  |  |                 key, value = d.popitem() | 
					
						
							|  |  |  |                 self.classes[module[0]] = value[1] | 
					
						
							|  |  |  |             else: | 
					
						
							|  |  |  |                 self.classes[module[0]] = d | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def run(self, models): | 
					
						
							|  |  |  |         self.basename = models.__name__ | 
					
						
							|  |  |  |         self.walker(("models", models)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def write(self): | 
					
						
							|  |  |  |         with open(self.filename, "w") as file: | 
					
						
							|  |  |  |             file.write("declare namespace {} {{\n".format(self.namespace)) | 
					
						
							|  |  |  |             for cls, params in self.classes.items(): | 
					
						
							|  |  |  |                 if isinstance(params, str): | 
					
						
							|  |  |  |                     file.write("\ttype {} = {};\n".format(cls, params)) | 
					
						
							|  |  |  |                 else: | 
					
						
							|  |  |  |                     file.write("\tinterface {} {{\n".format(cls)) | 
					
						
							|  |  |  |                     for name in params: | 
					
						
							|  |  |  |                         file.write("\t\t{}{}: {};\n".format(name, *params[name])) | 
					
						
							|  |  |  |                     file.write("\t}\n") | 
					
						
							|  |  |  |             file.write("}\n") | 
					
						
							| 
									
										
										
										
											2019-01-13 19:07:46 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-10-15 16:11:27 +00:00
										 |  |  | def install(arguments): | 
					
						
							|  |  |  |     app = create_app() | 
					
						
							|  |  |  |     with app.app_context(): | 
					
						
							|  |  |  |         install_all() | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-09-03 15:56:42 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-10-15 16:11:27 +00:00
										 |  |  | def run(arguments): | 
					
						
							| 
									
										
										
										
											2020-09-03 15:56:42 +00:00
										 |  |  |     app = create_app() | 
					
						
							| 
									
										
										
										
											2020-10-15 16:11:27 +00:00
										 |  |  |     with app.app_context(): | 
					
						
							| 
									
										
										
										
											2020-11-18 00:55:02 +00:00
										 |  |  |         app.wsgi_app = PrefixMiddleware(app.wsgi_app, prefix=config["FLASCHENGEIST"].get("root", "")) | 
					
						
							| 
									
										
										
										
											2020-10-15 16:11:27 +00:00
										 |  |  |         if arguments.debug: | 
					
						
							|  |  |  |             app.run(arguments.host, arguments.port, debug=True) | 
					
						
							| 
									
										
										
										
											2020-10-03 23:29:49 +00:00
										 |  |  |         else: | 
					
						
							| 
									
										
										
										
											2020-11-18 00:55:02 +00:00
										 |  |  |             app.run(arguments.host, arguments.port, debug=False) | 
					
						
							| 
									
										
										
										
											2020-10-15 16:11:27 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-10-18 23:42:54 +00:00
										 |  |  | def export(arguments): | 
					
						
							| 
									
										
										
										
											2020-10-30 02:30:46 +00:00
										 |  |  |     import flaschengeist.models as models | 
					
						
							| 
									
										
										
										
											2020-10-18 23:42:54 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |     app = create_app() | 
					
						
							|  |  |  |     with app.app_context(): | 
					
						
							| 
									
										
										
										
											2020-11-02 03:38:38 +00:00
										 |  |  |         gen = InterfaceGenerator(arguments.namespace, arguments.file) | 
					
						
							| 
									
										
										
										
											2021-05-26 14:47:03 +00:00
										 |  |  |         if not arguments.no_core: | 
					
						
							|  |  |  |             gen.run(models) | 
					
						
							|  |  |  |         if arguments.plugins is not None: | 
					
						
							| 
									
										
										
										
											2020-11-02 03:38:38 +00:00
										 |  |  |             for entry_point in pkg_resources.iter_entry_points("flaschengeist.plugin"): | 
					
						
							| 
									
										
										
										
											2021-05-26 14:47:03 +00:00
										 |  |  |                 if len(arguments.plugins) == 0 or entry_point.name in arguments.plugins: | 
					
						
							|  |  |  |                     plg = entry_point.load() | 
					
						
							|  |  |  |                     if hasattr(plg, "models") and plg.models is not None: | 
					
						
							|  |  |  |                         gen.run(plg.models) | 
					
						
							| 
									
										
										
										
											2020-11-02 03:38:38 +00:00
										 |  |  |         gen.write() | 
					
						
							| 
									
										
										
										
											2020-10-18 23:42:54 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-11-28 21:23:34 +00:00
										 |  |  | def ldap(arguments): | 
					
						
							| 
									
										
										
										
											2021-11-21 14:11:37 +00:00
										 |  |  |     app = create_app() | 
					
						
							|  |  |  |     with app.app_context(): | 
					
						
							| 
									
										
										
										
											2021-11-28 21:23:34 +00:00
										 |  |  |         if arguments.set_admin: | 
					
						
							|  |  |  |             from flaschengeist.controller import roleController | 
					
						
							|  |  |  |             from flaschengeist.database import db | 
					
						
							|  |  |  |             role = roleController.get(arguments.set_admin) | 
					
						
							|  |  |  |             role.permissions = roleController.get_permissions() | 
					
						
							|  |  |  |             db.session.commit() | 
					
						
							|  |  |  |         if arguments.sync: | 
					
						
							|  |  |  |             from flaschengeist.controller import userController | 
					
						
							|  |  |  |             from flaschengeist.plugins.auth_ldap import AuthLDAP | 
					
						
							|  |  |  |             from ldap3 import SUBTREE | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             auth_ldap: AuthLDAP = app.config.get("FG_PLUGINS").get("auth_ldap") | 
					
						
							|  |  |  |             if auth_ldap is None: | 
					
						
							|  |  |  |                 raise Exception("Plugin >auth_ldap< not found") | 
					
						
							| 
									
										
										
										
											2021-11-21 14:11:37 +00:00
										 |  |  |             conn = auth_ldap.ldap.connection | 
					
						
							|  |  |  |             if not conn: | 
					
						
							|  |  |  |                 conn = auth_ldap.ldap.connect(auth_ldap.root_dn, auth_ldap.root_secret) | 
					
						
							|  |  |  |             conn.search(auth_ldap.search_dn, "(uid=*)", SUBTREE, attributes=["uid", "givenName", "sn", "mail"]) | 
					
						
							|  |  |  |             ldap_users_response = conn.response | 
					
						
							|  |  |  |             for ldap_user in ldap_users_response: | 
					
						
							|  |  |  |                 uid = ldap_user["attributes"]["uid"][0] | 
					
						
							|  |  |  |                 userController.find_user(uid) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-10-18 23:42:54 +00:00
										 |  |  | if __name__ == "__main__": | 
					
						
							| 
									
										
										
										
											2020-10-15 16:11:27 +00:00
										 |  |  |     # create the top-level parser | 
					
						
							|  |  |  |     parser = argparse.ArgumentParser() | 
					
						
							| 
									
										
										
										
											2020-10-18 23:42:54 +00:00
										 |  |  |     subparsers = parser.add_subparsers(help="sub-command help", dest="sub_command") | 
					
						
							| 
									
										
										
										
											2020-10-15 16:11:27 +00:00
										 |  |  |     subparsers.required = True | 
					
						
							| 
									
										
										
										
											2020-10-18 23:42:54 +00:00
										 |  |  |     parser_run = subparsers.add_parser("run", help="run flaschengeist") | 
					
						
							| 
									
										
										
										
											2020-10-15 16:11:27 +00:00
										 |  |  |     parser_run.set_defaults(func=run) | 
					
						
							|  |  |  |     parser_run.add_argument("--host", help="set hostname to listen on", default="127.0.0.1") | 
					
						
							|  |  |  |     parser_run.add_argument("--port", help="set port to listen on", type=int, default=5000) | 
					
						
							|  |  |  |     parser_run.add_argument("--debug", help="run in debug mode", action="store_true") | 
					
						
							| 
									
										
										
										
											2020-10-18 23:42:54 +00:00
										 |  |  |     parser_install = subparsers.add_parser( | 
					
						
							|  |  |  |         "install", help="run database setup for flaschengeist and all installed plugins" | 
					
						
							|  |  |  |     ) | 
					
						
							| 
									
										
										
										
											2020-10-15 16:11:27 +00:00
										 |  |  |     parser_install.set_defaults(func=install) | 
					
						
							| 
									
										
										
										
											2020-10-18 23:42:54 +00:00
										 |  |  |     parser_export = subparsers.add_parser("export", help="export models to typescript interfaces") | 
					
						
							|  |  |  |     parser_export.set_defaults(func=export) | 
					
						
							|  |  |  |     parser_export.add_argument("--file", help="Filename where to save", default="flaschengeist.d.ts") | 
					
						
							| 
									
										
										
										
											2020-10-24 18:11:50 +00:00
										 |  |  |     parser_export.add_argument("--namespace", help="Namespace of declarations", default="FG") | 
					
						
							| 
									
										
										
										
											2021-05-26 14:47:03 +00:00
										 |  |  |     parser_export.add_argument( | 
					
						
							|  |  |  |         "--no-core", | 
					
						
							|  |  |  |         help="Do not export core declarations (only useful in conjunction with --plugins)", | 
					
						
							|  |  |  |         action="store_true", | 
					
						
							|  |  |  |     ) | 
					
						
							|  |  |  |     parser_export.add_argument("--plugins", help="Also export plugins (none means all)", nargs="*") | 
					
						
							| 
									
										
										
										
											2020-10-18 23:42:54 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-11-28 21:23:34 +00:00
										 |  |  |     parser_ldap = subparsers.add_parser("ldap", help="LDAP helper utils") | 
					
						
							|  |  |  |     parser_ldap.set_defaults(func=ldap) | 
					
						
							|  |  |  |     parser_ldap.add_argument('--sync', action="store_true", help="Sync ldap-users with database") | 
					
						
							|  |  |  |     parser_ldap.add_argument('--set-admin', type=str, help="Assign all permissions this to group") | 
					
						
							| 
									
										
										
										
											2021-11-21 14:11:37 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-10-15 16:11:27 +00:00
										 |  |  |     args = parser.parse_args() | 
					
						
							|  |  |  |     args.func(args) |