Added libs
This commit is contained in:
2
api/.gitignore
vendored
Normal file
2
api/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
__pycache__
|
||||
api.zip
|
||||
1
api/.mtimes.json
Normal file
1
api/.mtimes.json
Normal file
@@ -0,0 +1 @@
|
||||
{".gitignore": 1741162450.0110252, "api.py": 1757749878.4958198, "apiBlueprint.py": 1757238077.9332085, "certs.py": 1757228708.4552765, "config.json": 1756001769.9660056, "eventsSocketio.py": 1757328543.5948038, "requirements.txt": 1766641471.4877045, "__pycache__\\api.cpython-311.pyc": 1739790368.2421517}
|
||||
110
api/api.py
Normal file
110
api/api.py
Normal file
@@ -0,0 +1,110 @@
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
from flask import Flask, Blueprint, request, send_from_directory, jsonify
|
||||
from flask_cors import CORS, cross_origin
|
||||
from flask_socketio import SocketIO, Namespace
|
||||
|
||||
from libs.app.common.logging import get_logger
|
||||
from libs.app.common.paths import ROOT_DIR
|
||||
from libs.fspn.utils.wrapper_util import threaded
|
||||
from .apiBlueprint import ApiBlueprint
|
||||
from .eventsSocketio import EventsSocketio
|
||||
from libs.noSys.noSysModule import NoSysModule
|
||||
from .certs import generate_ca_and_cert, add_ca_os
|
||||
|
||||
logger = get_logger()
|
||||
|
||||
class Api(NoSysModule):
|
||||
def __init__(self, nosys_core):
|
||||
super().__init__(nosys_core)
|
||||
|
||||
self.dist_dir = os.path.join(ROOT_DIR, "libs/vueNoSys/dist")
|
||||
self.assets_dir = os.path.join(self.dist_dir, "assets")
|
||||
self.app = Flask(__name__, static_folder=self.assets_dir, template_folder=self.dist_dir)
|
||||
CORS(self.app, resources={r"/*": {"origins": "*"}}, supports_credentials=True)
|
||||
self.socketio = SocketIO(self.app, cors_allowed_origins="*")
|
||||
self.server = None
|
||||
|
||||
self.host = self.config["server"]["host"]
|
||||
self.port = self.config["server"]["port"]
|
||||
|
||||
self.register_blueprint(BasicBlueprint(self).blueprint)
|
||||
self.register_socketio(BasicEventSocketIo(self))
|
||||
|
||||
def setup(self):
|
||||
self.nosys_core.modules.api = self
|
||||
|
||||
certs_path = os.path.join(ROOT_DIR, "libs", "api", "certs")
|
||||
self.ca_path = os.path.join(certs_path , "ca.pem")
|
||||
self.ca_key_path = os.path.join(certs_path, "ca_key.pem")
|
||||
self.cert_path = os.path.join(certs_path, "cert.pem")
|
||||
self.key_path = os.path.join(certs_path, "key.pem")
|
||||
|
||||
if not os.path.exists(self.cert_path) or not os.path.exists(self.key_path) or not os.path.exists(self.ca_path) or not os.path.exists(self.ca_key_path):
|
||||
Path(certs_path).mkdir(parents=True, exist_ok=True)
|
||||
logger.debug("Generating certs")
|
||||
ca, cert, key = generate_ca_and_cert(self.ca_path, self.ca_key_path, self.cert_path, self.key_path)
|
||||
logger.debug("Adding ca to operational system")
|
||||
add_ca_os(self.ca_path)
|
||||
logger.debug("Cert installed")
|
||||
else:
|
||||
logger.debug("Certs already exists")
|
||||
|
||||
def register_blueprint(self, blueprint:Blueprint):
|
||||
try:
|
||||
self.app.register_blueprint(blueprint)
|
||||
logger.debug(f"Registered blueprint {blueprint.url_prefix}")
|
||||
except Exception:
|
||||
logger.exception(f"Failed registering blueprint {blueprint.url_prefix}")
|
||||
|
||||
def register_socketio(self, handler:EventsSocketio):
|
||||
try:
|
||||
handler.register_events(self.socketio)
|
||||
logger.debug(f"Registered socketio {handler.namespace}")
|
||||
except Exception:
|
||||
logger.exception(f"Failed registering socketio {handler.namespace}")
|
||||
|
||||
def on_nosys_ready(self, event):
|
||||
self.run()
|
||||
|
||||
@threaded
|
||||
def run(self):
|
||||
self.routes()
|
||||
logger.debug(f'Running Flask API ({self.host}:{self.port}) with urls: {self.app.url_map}')
|
||||
try:
|
||||
self.socketio.run(app=self.app, host=self.host, port=self.port, allow_unsafe_werkzeug=True, ssl_context=(self.cert_path, self.key_path))
|
||||
except Exception as e:
|
||||
logger.error(e)
|
||||
|
||||
def routes(self):
|
||||
@self.app.route("/", defaults={"path": ""})
|
||||
@self.app.route("/<path:path>")
|
||||
def index(path):
|
||||
if path != "" and os.path.exists(os.path.join(self.dist_dir, path)):
|
||||
return send_from_directory(self.dist_dir, path)
|
||||
return send_from_directory(self.dist_dir, "index.html")
|
||||
|
||||
class BasicBlueprint(ApiBlueprint):
|
||||
def routes(self):
|
||||
self.api:Api = self.module
|
||||
|
||||
@self.blueprint.route('/')
|
||||
def show():
|
||||
return "API"
|
||||
|
||||
class BasicEventSocketIo(EventsSocketio):
|
||||
def events(self):
|
||||
@self.on("connect")
|
||||
def on_connect(*args, **kwargs):
|
||||
print('API connected',args, kwargs, request.sid)
|
||||
self.emit("welcome", {"msg": f"Your id {request.sid}"})
|
||||
|
||||
@self.on("disconnect")
|
||||
def on_disconnect(*args, **kwargs):
|
||||
print('API disconnected',args, kwargs, request.sid)
|
||||
|
||||
@self.on("message")
|
||||
def on_message(*args, **kwargs):
|
||||
print('API Message',args, kwargs)
|
||||
self.emit("message", f"Message received {args[0]}")
|
||||
26
api/apiBlueprint.py
Normal file
26
api/apiBlueprint.py
Normal file
@@ -0,0 +1,26 @@
|
||||
from flask import Blueprint, jsonify
|
||||
from libs.noSys.noSysModule import NoSysModule
|
||||
from libs.noSys.events import Events
|
||||
from flask_socketio import SocketIO
|
||||
|
||||
class ApiBlueprint():
|
||||
def __init__(self, nosys_module:NoSysModule):
|
||||
self.module = nosys_module
|
||||
self.blueprint = Blueprint(self.module.name, __name__, url_prefix='/api/'+self.module.package_id)
|
||||
|
||||
self.default_routes()
|
||||
self.routes()
|
||||
|
||||
def default_routes(self):
|
||||
@self.blueprint.route('/health')
|
||||
def health_check():
|
||||
body = {"package":self.module.package_id, "moduleName":self.module.module_id}
|
||||
return jsonify(body)
|
||||
|
||||
@self.blueprint.route('/config')
|
||||
def config():
|
||||
body = self.module.nosys_core.config.get(self.module.package_id)
|
||||
return jsonify(body)
|
||||
|
||||
def routes(self):
|
||||
pass
|
||||
115
api/certs.py
Normal file
115
api/certs.py
Normal file
@@ -0,0 +1,115 @@
|
||||
import os
|
||||
import webview
|
||||
import ssl
|
||||
import ipaddress
|
||||
import pathlib
|
||||
from cryptography import x509
|
||||
from cryptography.x509.oid import NameOID
|
||||
from cryptography.hazmat.primitives import hashes, serialization
|
||||
from cryptography.hazmat.primitives.asymmetric import rsa
|
||||
from datetime import datetime, timedelta
|
||||
import os
|
||||
import platform
|
||||
import subprocess
|
||||
|
||||
def generate_ca_and_cert(ca_path="ca.pem", ca_key_path="ca_key.pem",
|
||||
cert_path="cert.pem", key_path="key.pem"):
|
||||
|
||||
ca_key = rsa.generate_private_key(public_exponent=65537, key_size=2048)
|
||||
ca_subject = x509.Name([
|
||||
x509.NameAttribute(NameOID.COUNTRY_NAME, u"US"),
|
||||
x509.NameAttribute(NameOID.ORGANIZATION_NAME, u"NoSys-CA"),
|
||||
x509.NameAttribute(NameOID.COMMON_NAME, u"NoSys Local CA"),
|
||||
])
|
||||
ca_cert = (
|
||||
x509.CertificateBuilder()
|
||||
.subject_name(ca_subject)
|
||||
.issuer_name(ca_subject)
|
||||
.public_key(ca_key.public_key())
|
||||
.serial_number(x509.random_serial_number())
|
||||
.not_valid_before(datetime.utcnow())
|
||||
.not_valid_after(datetime.utcnow() + timedelta(days=3650))
|
||||
.add_extension(
|
||||
x509.BasicConstraints(ca=True, path_length=None), critical=True,
|
||||
)
|
||||
.sign(ca_key, hashes.SHA256())
|
||||
)
|
||||
|
||||
with open(ca_path, "wb") as f:
|
||||
f.write(ca_cert.public_bytes(serialization.Encoding.PEM))
|
||||
with open(ca_key_path, "wb") as f:
|
||||
f.write(ca_key.private_bytes(
|
||||
encoding=serialization.Encoding.PEM,
|
||||
format=serialization.PrivateFormat.TraditionalOpenSSL,
|
||||
encryption_algorithm=serialization.NoEncryption(),
|
||||
))
|
||||
|
||||
key = rsa.generate_private_key(public_exponent=65537, key_size=2048)
|
||||
subject = x509.Name([
|
||||
x509.NameAttribute(NameOID.COUNTRY_NAME, u"US"),
|
||||
x509.NameAttribute(NameOID.ORGANIZATION_NAME, u"NoSys"),
|
||||
x509.NameAttribute(NameOID.COMMON_NAME, u"localhost"),
|
||||
])
|
||||
cert = (
|
||||
x509.CertificateBuilder()
|
||||
.subject_name(subject)
|
||||
.issuer_name(ca_subject)
|
||||
.public_key(key.public_key())
|
||||
.serial_number(x509.random_serial_number())
|
||||
.not_valid_before(datetime.utcnow())
|
||||
.not_valid_after(datetime.utcnow() + timedelta(days=3650))
|
||||
.add_extension(
|
||||
x509.SubjectAlternativeName([
|
||||
x509.DNSName(u"localhost"),
|
||||
x509.IPAddress(ipaddress.IPv4Address("127.0.0.1"))]),
|
||||
critical=False,
|
||||
)
|
||||
.sign(ca_key, hashes.SHA256())
|
||||
)
|
||||
|
||||
with open(cert_path, "wb") as f:
|
||||
f.write(cert.public_bytes(serialization.Encoding.PEM))
|
||||
with open(key_path, "wb") as f:
|
||||
f.write(key.private_bytes(
|
||||
encoding=serialization.Encoding.PEM,
|
||||
format=serialization.PrivateFormat.TraditionalOpenSSL,
|
||||
encryption_algorithm=serialization.NoEncryption(),
|
||||
))
|
||||
|
||||
return ca_path, cert_path, key_path
|
||||
|
||||
def add_ca_os(ca_path="ca.pem"):
|
||||
system = platform.system()
|
||||
if system == "Windows":
|
||||
add_ca_windows(ca_path)
|
||||
elif system == "Darwin":
|
||||
add_ca_macos(ca_path)
|
||||
elif system == "Linux":
|
||||
add_ca_linux(ca_path)
|
||||
else:
|
||||
raise Exception("Operational system not supported")
|
||||
|
||||
def add_ca_windows(ca_path="ca.pem"):
|
||||
subprocess.run([
|
||||
"powershell",
|
||||
"-Command",
|
||||
f'Import-Certificate -FilePath "{os.path.abspath(ca_path)}" -CertStoreLocation Cert:\\CurrentUser\\Root'
|
||||
], check=True)
|
||||
|
||||
def add_ca_macos(ca_path="ca.pem"):
|
||||
subprocess.run([
|
||||
"sudo",
|
||||
"security",
|
||||
"add-trusted-cert",
|
||||
"-d",
|
||||
"-r", "trustRoot",
|
||||
"-k", "/Library/Keychains/System.keychain",
|
||||
os.path.abspath(ca_path)
|
||||
], check=True)
|
||||
|
||||
def add_ca_linux(ca_path="ca.pem"):
|
||||
import shutil
|
||||
dest = "/usr/local/share/ca-certificates/zecho-ca.crt"
|
||||
shutil.copy(os.path.abspath(ca_path), dest)
|
||||
subprocess.run(["sudo", "update-ca-certificates"], check=True)
|
||||
|
||||
6
api/config.json
Normal file
6
api/config.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"server": {
|
||||
"host": "127.0.0.1",
|
||||
"port": "5050"
|
||||
}
|
||||
}
|
||||
51
api/eventsSocketio.py
Normal file
51
api/eventsSocketio.py
Normal file
@@ -0,0 +1,51 @@
|
||||
from datetime import datetime
|
||||
import time
|
||||
|
||||
from flask import Blueprint, make_response, request, jsonify, session, has_request_context
|
||||
from flask_socketio import SocketIO, Namespace, emit, send, join_room, leave_room
|
||||
|
||||
from libs.noSys.noSysModule import NoSysModule
|
||||
|
||||
class EventsSocketio():
|
||||
def __init__(self, module:NoSysModule):
|
||||
self.module = module
|
||||
self.namespace = f"/ws/{self.module.name}"
|
||||
self.socketio:SocketIO = None
|
||||
|
||||
def register_events(self, socketio:SocketIO):
|
||||
self.socketio = socketio
|
||||
self.default_events()
|
||||
self.events()
|
||||
|
||||
def default_events(self):
|
||||
@self.socketio.on("health", namespace=self.namespace)
|
||||
def on_health(*args, **kwargs):
|
||||
self.emit("health", {"status": "ok"})
|
||||
|
||||
@self.socketio.on("ping", namespace=self.namespace)
|
||||
def on_ping(data=None):
|
||||
self.emit("pong", {"ts": time.time(), "echo": data})
|
||||
|
||||
|
||||
def emit(self, event:str, data=None, room=None, **kwargs):
|
||||
target = None
|
||||
if room:
|
||||
target = room
|
||||
elif has_request_context():
|
||||
target = request.sid
|
||||
|
||||
self.socketio.emit(event, data, to=target, namespace=self.namespace, **kwargs)
|
||||
|
||||
def on(self, event: str):
|
||||
def decorator(handler):
|
||||
@self.socketio.on(event, namespace=self.namespace)
|
||||
def wrapper(*args, **kwargs):
|
||||
try:
|
||||
return handler(*args, **kwargs)
|
||||
except Exception as e:
|
||||
self.error(str(e))
|
||||
return wrapper
|
||||
return decorator
|
||||
|
||||
def events(self):
|
||||
pass
|
||||
10
api/info.json
Normal file
10
api/info.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"id": "api",
|
||||
"version": 0.042,
|
||||
"modules": [
|
||||
{
|
||||
"id": "api",
|
||||
"version": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
4
api/requirements.txt
Normal file
4
api/requirements.txt
Normal file
@@ -0,0 +1,4 @@
|
||||
flask
|
||||
flask-socketio
|
||||
python-socketio[client]
|
||||
flask_cors
|
||||
Reference in New Issue
Block a user