diff --git a/bumper/__init__.py b/bumper/__init__.py index 319de2f..7578464 100644 --- a/bumper/__init__.py +++ b/bumper/__init__.py @@ -6,7 +6,7 @@ import socket import sys from typing import Optional -from bumper.confserver import ConfServer +from bumper.confserver import ConfServer, WebServerBinding from bumper.db import * from bumper.models import * from bumper.mqttserver import MQTTHelperBot, MQTTServer @@ -53,11 +53,10 @@ token_validity_seconds = 3600 # 1 hour oauth_validity_days = 15 db = None -mqtt_server: Optional[MQTTServer] = None -mqtt_helperbot: Optional[MQTTHelperBot] = None -conf_server: Optional[ConfServer] = None -conf_server_2: Optional[ConfServer] = None -xmpp_server: Optional[XMPPServer] = None +mqtt_server: MQTTServer +mqtt_helperbot: MQTTHelperBot +conf_server: ConfServer +xmpp_server: XMPPServer # Plugins sys.path.append(os.path.join(bumper_dir, "bumper", "plugins")) @@ -75,9 +74,11 @@ bumperlog = get_logger("bumper") logging.getLogger("asyncio").setLevel(logging.CRITICAL + 1) # Ignore this logger mqtt_listen_port = 8883 -conf1_listen_port = 443 -conf2_listen_port = 8007 xmpp_listen_port = 5223 +conf_server_bindings = [ + WebServerBinding(bumper_listen, 8443, True), + WebServerBinding(bumper_listen, 8007, False), +] async def start(): @@ -117,9 +118,7 @@ async def start(): global mqtt_helperbot mqtt_helperbot = MQTTHelperBot(bumper_listen, mqtt_listen_port) global conf_server - conf_server = ConfServer((bumper_listen, conf1_listen_port), usessl=True) - global conf_server_2 - conf_server_2 = ConfServer((bumper_listen, conf2_listen_port), usessl=False) + conf_server = ConfServer(conf_server_bindings) global xmpp_server xmpp_server = XMPPServer((bumper_listen, xmpp_listen_port)) @@ -138,17 +137,7 @@ async def start(): await asyncio.sleep(0.1) # Start web servers - conf_server.confserver_app() - asyncio.create_task( - conf_server.start_site( - conf_server.app, address=bumper_listen, port=conf1_listen_port, usessl=True - ) - ) - asyncio.create_task( - conf_server.start_site( - conf_server.app, address=bumper_listen, port=conf2_listen_port, usessl=False - ) - ) + await conf_server.start() # Start maintenance while not shutting_down: @@ -165,8 +154,7 @@ async def shutdown(): try: bumperlog.info("Shutting down") - await conf_server.stop_server() - await conf_server_2.stop_server() + await conf_server.shutdown() if mqtt_server.state == "started": await mqtt_server.shutdown() elif mqtt_server.state == "starting": diff --git a/bumper/confserver.py b/bumper/confserver.py index a8e68fb..13dd9d6 100644 --- a/bumper/confserver.py +++ b/bumper/confserver.py @@ -1,25 +1,36 @@ -#!/usr/bin/env python3 +"""Web server module.""" import asyncio +import dataclasses +import inspect +import json import logging import os import ssl +from typing import Union import aiohttp_jinja2 import jinja2 from aiohttp import web +from aiohttp.typedefs import Handler +from aiohttp.web_exceptions import ( + HTTPInternalServerError, + HTTPNoContent, + HTTPBadRequest, +) +from aiohttp.web_request import Request +from aiohttp.web_response import Response, StreamResponse -from bumper import plugins -from bumper.models import * +import bumper +from .plugins import ConfServerApp from .util import get_logger -class aiohttp_filter(logging.Filter): - def filter(self, record): - if ( - record.name == "aiohttp.access" and record.levelno == 20 - ): # Filters aiohttp.access log to switch it from INFO to DEBUG +class _aiohttp_filter(logging.Filter): + def filter(self, record: logging.LogRecord) -> bool: + if record.name == "aiohttp.access" and record.levelno == 20: + # Filters aiohttp.access log to switch it from INFO to DEBUG record.levelno = 10 record.levelname = "DEBUG" @@ -30,53 +41,64 @@ class aiohttp_filter(logging.Filter): confserverlog = get_logger("confserver") -logging.getLogger("aiohttp.access").addFilter( - aiohttp_filter() -) # Add logging filter above to aiohttp.access +# Add logging filter above to aiohttp.access +logging.getLogger("aiohttp.access").addFilter(_aiohttp_filter()) + + +@dataclasses.dataclass(frozen=True) +class WebServerBinding: + """Web server binding.""" + + host: str + port: int + use_ssl: bool class ConfServer: - def __init__(self, address, usessl=False): - self.usessl = usessl - self.address = address - self.app = None - self.site = None - self.runner = None - self.runners = [] - self.excludelogging = ["base", "remove-bot", "remove-client", "restart-service"] + """Web server.""" - def get_milli_time(self, timetoconvert): - return int(round(timetoconvert * 1000)) + _EXCLUDE_FROM_LOGGING = ["base", "remove-bot", "remove-client", "restart-service"] - def confserver_app(self): - self.app = web.Application( + def __init__(self, bindings: Union[list[WebServerBinding], WebServerBinding]): + self._runners: list[web.AppRunner] = [] + + if isinstance(bindings, WebServerBinding): + bindings = [bindings] + self._bindings = bindings + + self._app = web.Application( middlewares=[ - self.log_all_requests, + self._log_all_requests, ], ) aiohttp_jinja2.setup( - self.app, + self._app, loader=jinja2.FileSystemLoader( os.path.join(bumper.bumper_dir, "bumper", "web", "templates") ), ) + self._add_routes() + self._app.freeze() # no modification allowed anymore - self.app.add_routes( + def _add_routes(self) -> None: + self._app.add_routes( [ - web.get("", self.handle_base, name="base"), - web.get("/bot/remove/{did}", self.handle_RemoveBot, name="remove-bot"), + web.get("", self._handle_base, name="base"), + web.get( + "/bot/remove/{did}", self._handle_remove_bot, name="remove-bot" + ), web.get( "/client/remove/{resource}", - self.handle_RemoveClient, + self._handle_remove_client, name="remove-client", ), web.get( "/restart_{service}", - self.handle_RestartService, + self._handle_restart_service, name="restart-service", ), - web.post("/lookup.do", self.handle_lookup), - web.post("/newauth.do", self.handle_newauth), + web.post("/lookup.do", self._handle_lookup), + web.post("/newauth.do", self._handle_newauth), ] ) @@ -94,11 +116,17 @@ class ConfServer: } # Load plugins - for plug in bumper.discovered_plugins: - if isinstance( - bumper.discovered_plugins[plug].plugin, bumper.plugins.ConfServerApp - ): - plugin = bumper.discovered_plugins[plug].plugin + for module in bumper.discovered_plugins.values(): + plugins = [ + m[1] + for m in inspect.getmembers(module, inspect.isclass) + if m[1].__module__ == module.__name__ + ] + for plugin_type in plugins: + if not issubclass(plugin_type, ConfServerApp): + continue + + plugin = plugin_type() if plugin.plugin_type == "sub_api": # app or sub_api if plugin.sub_api in apis: if plugin.routes: @@ -108,73 +136,51 @@ class ConfServer: elif plugin.plugin_type == "app": if plugin.path_prefix and plugin.app: logging.debug(f"Adding confserver plugin ({plugin.name})") - self.app.add_subapp(plugin.path_prefix, plugin.app) + self._app.add_subapp(plugin.path_prefix, plugin.app) for api in apis: - self.app.add_subapp(apis[api]["prefix"], apis[api]["app"]) + self._app.add_subapp(apis[api]["prefix"], apis[api]["app"]) - # for resource in self.app.router.resources(): - # print(resource) - - async def start_site(self, app, address="localhost", port=8080, usessl=False): + async def start(self) -> None: + """Start server.""" try: - runner = web.AppRunner(app) - self.runners.append(runner) - await runner.setup() - if usessl: - ssl_ctx = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH) - ssl_ctx.load_cert_chain(bumper.server_cert, bumper.server_key) + confserverlog.info("Starting ConfServer") + for binding in self._bindings: + runner = web.AppRunner(self._app) + self._runners.append(runner) + await runner.setup() + + ssl_ctx = None + if binding.use_ssl: + ssl_ctx = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH) + ssl_ctx.load_cert_chain(bumper.server_cert, bumper.server_key) + site = web.TCPSite( runner, - host=address, - port=port, + host=binding.host, + port=binding.port, ssl_context=ssl_ctx, ) - else: - site = web.TCPSite(runner, host=address, port=port) - - await site.start() + await site.start() except Exception as e: confserverlog.exception(f"{e}") raise e - async def start_server(self): + async def shutdown(self) -> None: + """Shutdown server.""" try: - confserverlog.info( - f"Starting ConfServer at {self.address[0]}:{self.address[1]}" - ) - self.runner = web.AppRunner(self.app) - await self.runner.setup() + confserverlog.info("Shutting down") + for runner in self._runners: + await runner.shutdown() - if self.usessl: - ssl_ctx = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH) - ssl_ctx.load_cert_chain(bumper.server_cert, bumper.server_key) - self.site = web.TCPSite( - self.runner, - host=self.address[0], - port=self.address[1], - ssl_context=ssl_ctx, - ) - - else: - self.site = web.TCPSite( - self.runner, host=self.address[0], port=self.address[1] - ) - - await self.site.start() - except Exception as e: - confserverlog.exception(f"{e}") - raise e - - async def stop_server(self): - try: - await self.runner.shutdown() + self._runners.clear() + await self._app.shutdown() except Exception as e: confserverlog.exception(f"{e}") - async def handle_base(self, request): + async def _handle_base(self, request: Request) -> Response: try: bots = bumper.db_get().table("bots").all() clients = bumper.db_get().table("clients").all() @@ -196,20 +202,21 @@ class ConfServer: "sessions": { "count": len(mq_sessions), "clients": mq_sessions, - } + }, }, "xmpp_server": bumper.xmpp_server, } - resp = aiohttp_jinja2.render_template("home.jinja2", request, context=all) - return resp - + return aiohttp_jinja2.render_template("home.jinja2", request, context=all) except Exception as e: confserverlog.exception(f"{e}") - @web.middleware - async def log_all_requests(self, request, handler): + raise HTTPInternalServerError - if request._match_info.route.name not in self.excludelogging: + @web.middleware + async def _log_all_requests( + self, request: Request, handler: Handler + ) -> StreamResponse: + if request._match_info.route.name not in self._EXCLUDE_FROM_LOGGING: to_log = { "request": { "route_name": f"{request.match_info.route.name}", @@ -230,8 +237,8 @@ class ConfServer: try: postbody = json.loads(await request.text()) except Exception as e: - confserverlog.error(f"Request body not json: {e} - {e.doc}") - postbody = e.doc + confserverlog.error(f"Request body not json: {e}") + raise HTTPBadRequest(reason="Body was not json") else: postbody = await request.post() @@ -242,12 +249,15 @@ class ConfServer: if response is None: confserverlog.warning("Response was null!") confserverlog.warning(json.dumps(to_log)) - return response + raise HTTPNoContent to_log["response"] = { "status": f"{response.status}", } - if not "application/octet-stream" in response.content_type: + if ( + "application/octet-stream" not in response.content_type + and isinstance(response, Response) + ): to_log["response"]["body"] = f"{json.loads(response.body)}" confserverlog.debug(json.dumps(to_log)) @@ -262,16 +272,16 @@ class ConfServer: except Exception as e: confserverlog.exception(f"{e}") confserverlog.error(json.dumps(to_log)) - return e + raise e else: return await handler(request) - async def restart_Helper(self): + async def _restart_helper_bot(self) -> None: await bumper.mqtt_helperbot.disconnect() asyncio.create_task(bumper.mqtt_helperbot.start()) - async def restart_MQTT(self): + async def _restart_mqtt_server(self) -> None: loop = asyncio.get_event_loop() if bumper.mqtt_server.state not in ["stopped", "not_started"]: @@ -287,35 +297,31 @@ class ConfServer: loop.call_later(1.5, lambda: asyncio.create_task(bumper.mqtt_server.start())) - async def restart_XMPP(self): - bumper.xmpp_server.disconnect() - await bumper.xmpp_server.start_async_server() - - async def handle_RestartService(self, request): + async def _handle_restart_service(self, request: Request) -> Response: try: service = request.match_info.get("service", "") if service == "Helperbot": - await self.restart_Helper() + await self._restart_helper_bot() return web.json_response({"status": "complete"}) - elif service == "MQTTServer": - asyncio.create_task(self.restart_MQTT()) + if service == "MQTTServer": + asyncio.create_task(self._restart_mqtt_server()) aloop = asyncio.get_event_loop() aloop.call_later( - 5, lambda: asyncio.create_task(self.restart_Helper()) + 5, lambda: asyncio.create_task(self._restart_helper_bot()) ) # In 5 seconds restart Helperbot return web.json_response({"status": "complete"}) - elif service == "XMPPServer": - await self.restart_XMPP() + if service == "XMPPServer": + bumper.xmpp_server.disconnect() + await bumper.xmpp_server.start_async_server() return web.json_response({"status": "complete"}) - else: - return web.json_response({"status": "invalid service"}) + return web.json_response({"status": "invalid service"}) except Exception as e: confserverlog.exception(f"{e}") - pass + raise - async def handle_RemoveBot(self, request): + async def _handle_remove_bot(self, request: Request) -> Response: try: did = request.match_info.get("did", "") bumper.bot_remove(did) @@ -326,9 +332,10 @@ class ConfServer: except Exception as e: confserverlog.exception(f"{e}") - pass - async def handle_RemoveClient(self, request): + raise HTTPInternalServerError + + async def _handle_remove_client(self, request: Request) -> Response: try: resource = request.match_info.get("resource", "") bumper.client_remove(resource) @@ -339,93 +346,20 @@ class ConfServer: except Exception as e: confserverlog.exception(f"{e}") - pass - async def handle_login(self, request): + raise HTTPInternalServerError + + async def _handle_lookup(self, request: Request) -> Response: try: - user_devid = request.match_info.get("devid", "") - countrycode = request.match_info.get("country", "us") - apptype = request.match_info.get("apptype", "") - confserverlog.info(f"client with devid {user_devid} attempting login") - if bumper.use_auth: - if ( - not user_devid == "" - ): # Performing basic "auth" using devid, super insecure - user = bumper.user_by_deviceid(user_devid) - if "checkLogin" in request.path: - self.check_token( - apptype, countrycode, user, request.query["accessToken"] - ) - else: - if "global_" in apptype: # EcoVacs Home - login_details = EcoVacsHome_Login() - login_details.ucUid = "fuid_{}".format(user["userid"]) - login_details.loginName = "fusername_{}".format( - user["userid"] - ) - login_details.mobile = None - - else: - login_details = EcoVacs_Login() - - # Deactivate old tokens and authcodes - bumper.user_revoke_expired_tokens(user["userid"]) - - login_details.accessToken = self.generate_token(user) - login_details.uid = "fuid_{}".format(user["userid"]) - login_details.username = "fusername_{}".format(user["userid"]) - login_details.country = countrycode - login_details.email = "null@null.com" - - body = { - "code": API_ERRORS[RETURN_API_SUCCESS], - "data": json.loads(login_details.toJSON()), - # { - # "accessToken": self.generate_token(tmpuser), # Generate a token - # "country": countrycode, - # "email": "null@null.com", - # "uid": "fuid_{}".format(tmpuser["userid"]), - # "username": "fusername_{}".format(tmpuser["userid"]), - # }, - "msg": "操作成功", - "time": self.get_milli_time(datetime.utcnow().timestamp()), - } - - return web.json_response(body) - - body = { - "code": bumper.ERR_USER_NOT_ACTIVATED, - "data": None, - "msg": "当前密码错误", - "time": self.get_milli_time(datetime.utcnow().timestamp()), - } - - return web.json_response(body) - - else: - return web.json_response( - self._auth_any(user_devid, apptype, countrycode, request) - ) - - except Exception as e: - confserverlog.exception(f"{e}") - - async def handle_lookup(self, request): - try: - - body = {} - postbody = {} if request.content_type == "application/x-www-form-urlencoded": - postbody = await request.post() - + body = await request.post() else: - postbody = json.loads(await request.text()) + body = json.loads(await request.text()) - confserverlog.debug(postbody) + confserverlog.debug(body) - todo = postbody["todo"] - if todo == "FindBest": - service = postbody["service"] + if body["todo"] == "FindBest": + service = body["service"] if service == "EcoMsgNew": srvip = bumper.bumper_announce_ip srvport = 5223 @@ -434,13 +368,10 @@ class ConfServer: srvip, srvport ) ) - msgserver = {"ip": srvip, "port": srvport, "result": "ok"} - msgserver = json.dumps(msgserver) - msgserver = msgserver.replace( - " ", "" - ) # bot seems to be very picky about having no spaces, only way was with text - - return web.json_response(text=msgserver) + server = json.dumps({"ip": srvip, "port": srvport, "result": "ok"}) + # bot seems to be very picky about having no spaces, only way was with text + server = server.replace(" ", "") + return web.json_response(text=server) elif service == "EcoUpdate": srvip = "47.88.66.164" # EcoVacs Server @@ -450,14 +381,18 @@ class ConfServer: srvip, srvport ) ) - body = {"result": "ok", "ip": srvip, "port": srvport} + return web.json_response( + {"result": "ok", "ip": srvip, "port": srvport} + ) - return web.json_response(body) + return web.json_response({}) except Exception as e: confserverlog.exception(f"{e}") - async def handle_newauth(self, request): + raise HTTPInternalServerError + + async def _handle_newauth(self, request: Request) -> Response: # Bumper is only returning the submitted token. No reason yet to create another new token try: if request.content_type == "application/x-www-form-urlencoded": @@ -474,401 +409,4 @@ class ConfServer: except Exception as e: confserverlog.exception(f"{e}") - async def disconnect(self): - try: - confserverlog.info("shutting down") - await self.app.shutdown() - - except Exception as e: - confserverlog.exception(f"{e}") - - class ConfServer_GeneralFunctions: - def __init__(self): - pass - - def get_milli_time(self, timetoconvert): - return int(round(timetoconvert * 1000)) - - class ConfServer_AuthHandler: - def __init__(self): - self.get_milli_time = ( - bumper.ConfServer.ConfServer_GeneralFunctions().get_milli_time - ) - pass - - def generate_token(self, user): - try: - tmpaccesstoken = uuid.uuid4().hex - bumper.user_add_token(user["userid"], tmpaccesstoken) - return tmpaccesstoken - - except Exception as e: - confserverlog.exception(f"{e}") - - def generate_authcode(self, user, countrycode, token): - try: - tmpauthcode = f"{countrycode}_{uuid.uuid4().hex}" - bumper.user_add_authcode(user["userid"], token, tmpauthcode) - return tmpauthcode - - except Exception as e: - confserverlog.exception(f"{e}") - - async def login(self, request): - try: - user_devid = request.match_info.get("devid", "") - countrycode = request.match_info.get("country", "us") - apptype = request.match_info.get("apptype", "") - confserverlog.info(f"client with devid {user_devid} attempting login") - if bumper.use_auth: - if ( - not user_devid == "" - ): # Performing basic "auth" using devid, super insecure - user = bumper.user_by_deviceid(user_devid) - if "checkLogin" in request.path: - self.check_token( - apptype, countrycode, user, request.query["accessToken"] - ) - else: - if "global_" in apptype: # EcoVacs Home - login_details = EcoVacsHome_Login() - login_details.ucUid = "fuid_{}".format(user["userid"]) - login_details.loginName = "fusername_{}".format( - user["userid"] - ) - login_details.mobile = None - - else: - login_details = EcoVacs_Login() - - # Deactivate old tokens and authcodes - bumper.user_revoke_expired_tokens(user["userid"]) - - login_details.accessToken = self.generate_token(user) - login_details.uid = "fuid_{}".format(user["userid"]) - login_details.username = "fusername_{}".format( - user["userid"] - ) - login_details.country = countrycode - login_details.email = "null@null.com" - - body = { - "code": API_ERRORS[RETURN_API_SUCCESS], - "data": json.loads(login_details.toJSON()), - # { - # "accessToken": self.generate_token(tmpuser), # Generate a token - # "country": countrycode, - # "email": "null@null.com", - # "uid": "fuid_{}".format(tmpuser["userid"]), - # "username": "fusername_{}".format(tmpuser["userid"]), - # }, - "msg": "操作成功", - "time": self.get_milli_time( - datetime.utcnow().timestamp() - ), - } - - return web.json_response(body) - - body = { - "code": bumper.ERR_USER_NOT_ACTIVATED, - "data": None, - "msg": "当前密码错误", - "time": self.get_milli_time(datetime.utcnow().timestamp()), - } - - return web.json_response(body) - - else: - return web.json_response( - self._auth_any(user_devid, apptype, countrycode, request) - ) - - except Exception as e: - confserverlog.exception(f"{e}") - - async def get_AuthCode(self, request): - try: - apptype = request.match_info.get("apptype", "") - user_devid = request.match_info.get("devid", "") # Ecovacs - if user_devid == "": - user_devid = request.query["deviceId"] # Ecovacs Home - - if not user_devid == "": - user = bumper.user_by_deviceid(user_devid) - token = "" - if user: - if "accessToken" in request.query: - token = bumper.user_get_token( - user["userid"], request.query["accessToken"] - ) - if token: - authcode = "" - if not "authcode" in token: - authcode = self.generate_authcode( - user, - request.match_info.get("country", "us"), - request.query["accessToken"], - ) - else: - authcode = token["authcode"] - if "global" in apptype: - body = { - "code": bumper.RETURN_API_SUCCESS, - "data": { - "authCode": authcode, - "ecovacsUid": request.query["uid"], - }, - "msg": "操作成功", - "success": True, - "time": self.get_milli_time( - datetime.utcnow().timestamp() - ), - } - else: - body = { - "code": bumper.RETURN_API_SUCCESS, - "data": { - "authCode": authcode, - "ecovacsUid": request.query["uid"], - }, - "msg": "操作成功", - "time": self.get_milli_time( - datetime.utcnow().timestamp() - ), - } - return web.json_response(body) - - body = { - "code": bumper.ERR_TOKEN_INVALID, - "data": None, - "msg": "当前密码错误", - "time": self.get_milli_time(datetime.utcnow().timestamp()), - } - - return web.json_response(body) - - except Exception as e: - confserverlog.exception(f"{e}") - - def check_token(self, apptype, countrycode, user, token): - try: - if bumper.check_token(user["userid"], token): - - if "global_" in apptype: # EcoVacs Home - login_details = EcoVacsHome_Login() - login_details.ucUid = "fuid_{}".format(user["userid"]) - login_details.loginName = "fusername_{}".format(user["userid"]) - login_details.mobile = None - else: - login_details = EcoVacs_Login() - - login_details.accessToken = token - login_details.uid = "fuid_{}".format(user["userid"]) - login_details.username = "fusername_{}".format(user["userid"]) - login_details.country = countrycode - login_details.email = "null@null.com" - - body = { - "code": bumper.RETURN_API_SUCCESS, - "data": json.loads(login_details.toJSON()), - # { - # "accessToken": self.generate_token(tmpuser), # Generate a token - # "country": countrycode, - # "email": "null@null.com", - # "uid": "fuid_{}".format(tmpuser["userid"]), - # "username": "fusername_{}".format(tmpuser["userid"]), - # }, - "msg": "操作成功", - "time": self.get_milli_time(datetime.utcnow().timestamp()), - } - return web.json_response(body) - - else: - body = { - "code": bumper.ERR_TOKEN_INVALID, - "data": None, - "msg": "当前密码错误", - "time": self.get_milli_time(datetime.utcnow().timestamp()), - } - return web.json_response(body) - - except Exception as e: - confserverlog.exception(f"{e}") - - def _auth_any(self, devid, apptype, country, request): - try: - user_devid = devid - countrycode = country - user = bumper.user_by_deviceid(user_devid) - bots = bumper.db_get().table("bots").all() - - if user: # Default to user 0 - tmpuser = user - if "global_" in apptype: # EcoVacs Home - login_details = EcoVacsHome_Login() - login_details.ucUid = "fuid_{}".format(tmpuser["userid"]) - login_details.loginName = "fusername_{}".format( - tmpuser["userid"] - ) - login_details.mobile = None - else: - login_details = EcoVacs_Login() - - login_details.accessToken = self.generate_token(tmpuser) - login_details.uid = "fuid_{}".format(tmpuser["userid"]) - login_details.username = "fusername_{}".format(tmpuser["userid"]) - login_details.country = countrycode - login_details.email = "null@null.com" - bumper.user_add_device(tmpuser["userid"], user_devid) - else: - bumper.user_add("tmpuser") # Add a new user - tmpuser = bumper.user_get("tmpuser") - if "global_" in apptype: # EcoVacs Home - login_details = EcoVacsHome_Login() - login_details.ucUid = "fuid_{}".format(tmpuser["userid"]) - login_details.loginName = "fusername_{}".format( - tmpuser["userid"] - ) - login_details.mobile = None - else: - login_details = EcoVacs_Login() - - login_details.accessToken = self.generate_token(tmpuser) - login_details.uid = "fuid_{}".format(tmpuser["userid"]) - login_details.username = "fusername_{}".format(tmpuser["userid"]) - login_details.country = countrycode - login_details.email = "null@null.com" - bumper.user_add_device(tmpuser["userid"], user_devid) - - for bot in bots: # Add all bots to the user - if "did" in bot: - bumper.user_add_bot(tmpuser["userid"], bot["did"]) - else: - confserverlog.error(f"No DID for bot: {bot}") - - if ( - "checkLogin" in request.path - ): # If request was to check a token do so - checkToken = self.check_token( - apptype, countrycode, tmpuser, request.query["accessToken"] - ) - isGood = json.loads(checkToken.text) - if isGood["code"] == "0000": - return isGood - - # Deactivate old tokens and authcodes - bumper.user_revoke_expired_tokens(tmpuser["userid"]) - - body = { - "code": bumper.RETURN_API_SUCCESS, - "data": json.loads(login_details.toJSON()), - # { - # "accessToken": self.generate_token(tmpuser), # Generate a token - # "country": countrycode, - # "email": "null@null.com", - # "uid": "fuid_{}".format(tmpuser["userid"]), - # "username": "fusername_{}".format(tmpuser["userid"]), - # }, - "msg": "操作成功", - "time": self.get_milli_time(datetime.utcnow().timestamp()), - } - - return body - - except Exception as e: - confserverlog.exception(f"{e}") - - def getUserAccountInfo(self, request): - try: - user_devid = request.match_info.get("devid", "") - countrycode = request.match_info.get("country", "us") - apptype = request.match_info.get("apptype", "") - user = bumper.user_by_deviceid(user_devid) - - if "global_" in apptype: # EcoVacs Home - login_details = EcoVacsHome_Login() - login_details.ucUid = "fuid_{}".format(user["userid"]) - login_details.loginName = "fusername_{}".format(user["userid"]) - login_details.mobile = None - else: - login_details = EcoVacs_Login() - - login_details.uid = "fuid_{}".format(user["userid"]) - login_details.username = "fusername_{}".format(user["userid"]) - login_details.country = countrycode - login_details.email = "null@null.com" - - body = { - "code": bumper.RETURN_API_SUCCESS, - "data": { - "email": login_details.email, - "hasMobile": "N", - "hasPassword": "Y", - "uid": login_details.uid, - "userName": login_details.username, - "obfuscatedMobile": None, - "mobile": None, - "loginName": login_details.loginName, - }, - "msg": "操作成功", - "time": self.get_milli_time(datetime.utcnow().timestamp()), - } - - # Example body - # { - # "code": "0000", - # "data": { - # "email": "user@gmail.com", - # "hasMobile": "N", - # "hasPassword": "Y", - # "headIco": "", - # "loginName": "user@gmail.com", - # "mobile": null, - # "mobileAreaNo": null, - # "nickname": "", - # "obfuscatedMobile": null, - # "thirdLoginInfoList": [ - # { - # "accountType": "WeChat", - # "hasBind": "N" - # } - # ], - # "uid": "20180719212155_*****", - # "userName": "EAY*****" - # }, - # "msg": "操作成功", - # "success": true, - # "time": 1578203898343 - # } - - return web.json_response(body) - - except Exception as e: - confserverlog.exception(f"{e}") - - async def logout(self, request): - try: - user_devid = request.match_info.get("devid", "") - if not user_devid == "": - user = bumper.user_by_deviceid(user_devid) - if user: - if bumper.check_token( - user["userid"], request.query["accessToken"] - ): - # Deactivate old tokens and authcodes - bumper.user_revoke_token( - user["userid"], request.query["accessToken"] - ) - - body = { - "code": bumper.RETURN_API_SUCCESS, - "data": None, - "msg": "操作成功", - "time": self.get_milli_time(datetime.utcnow().timestamp()), - } - - return web.json_response(body) - - except Exception as e: - confserverlog.exception(f"{e}") + raise HTTPInternalServerError diff --git a/bumper/db.py b/bumper/db.py index 3f48fa2..b79e34b 100644 --- a/bumper/db.py +++ b/bumper/db.py @@ -31,7 +31,7 @@ def os_db_path(): # createdir=True): return os.path.join(bumper.data_dir, "bumper.db") -def db_get(): +def db_get() -> TinyDB: # Will create the database if it doesn't exist db = TinyDB(db_file()) @@ -340,7 +340,7 @@ def bot_add(sn: str, did: str, devclass: str, resource: str, company: str) -> No bot_full_upsert(newbot.asdict()) -def bot_remove(did): +def bot_remove(did: str): bots = db_get().table("bots") bot = bot_get(did) if bot: @@ -392,7 +392,7 @@ def client_add(userid: str, realm: str, resource: str) -> None: client_full_upsert(newclient.asdict()) -def client_remove(resource): +def client_remove(resource: str): clients = db_get().table("clients") client = client_get(resource) if client: diff --git a/bumper/models.py b/bumper/models.py index 129637e..c954c90 100644 --- a/bumper/models.py +++ b/bumper/models.py @@ -8,6 +8,7 @@ from typing import Any from amqtt.session import Session import bumper +from bumper.util import convert_to_millis class VacBotDevice: @@ -116,9 +117,7 @@ class OAuth: def toResponse(self): data = self.__dict__ - data[ - "expire_at" - ] = bumper.ConfServer.ConfServer_GeneralFunctions().get_milli_time( + data["expire_at"] = convert_to_millis( datetime.fromisoformat(self.expire_at).timestamp() ) return data diff --git a/bumper/plugins/bumper_confserver_portal_appsvr.py b/bumper/plugins/bumper_confserver_portal_appsvr.py index 04a603d..450d3a9 100644 --- a/bumper/plugins/bumper_confserver_portal_appsvr.py +++ b/bumper/plugins/bumper_confserver_portal_appsvr.py @@ -1,13 +1,14 @@ #!/usr/bin/env python3 +import json import logging from aiohttp import web -from bumper import plugins -from bumper.models import * +import bumper +from bumper.plugins import ConfServerApp -class portal_api_appsvr(plugins.ConfServerApp): +class portal_api_appsvr(ConfServerApp): def __init__(self): self.name = "portal_api_appsvr" self.plugin_type = "sub_api" @@ -34,10 +35,6 @@ class portal_api_appsvr(plugins.ConfServerApp): ), ] - self.get_milli_time = ( - bumper.ConfServer.ConfServer_GeneralFunctions().get_milli_time - ) - async def handle_appsvr_app(self, request): if not request.method == "GET": # Skip GET for now try: @@ -211,6 +208,3 @@ class portal_api_appsvr(plugins.ConfServerApp): except Exception as e: logging.exception(f"{e}") - - -plugin = portal_api_appsvr() diff --git a/bumper/plugins/bumper_confserver_portal_dim.py b/bumper/plugins/bumper_confserver_portal_dim.py index 32da818..e3b1da1 100644 --- a/bumper/plugins/bumper_confserver_portal_dim.py +++ b/bumper/plugins/bumper_confserver_portal_dim.py @@ -27,10 +27,6 @@ class portal_api_dim(plugins.ConfServerApp): ), ] - self.get_milli_time = ( - bumper.ConfServer.ConfServer_GeneralFunctions().get_milli_time - ) - async def handle_dim_devmanager(self, request): # Used in EcoVacs Home App try: json_body = json.loads(await request.text()) @@ -76,6 +72,3 @@ class portal_api_dim(plugins.ConfServerApp): except Exception as e: logging.exception(f"{e}") - - -plugin = portal_api_dim() diff --git a/bumper/plugins/bumper_confserver_portal_ecms.py b/bumper/plugins/bumper_confserver_portal_ecms.py index ead6976..5edebb1 100644 --- a/bumper/plugins/bumper_confserver_portal_ecms.py +++ b/bumper/plugins/bumper_confserver_portal_ecms.py @@ -22,10 +22,6 @@ class portal_api_ecms(plugins.ConfServerApp): ), ] - self.get_milli_time = ( - bumper.ConfServer.ConfServer_GeneralFunctions().get_milli_time - ) - async def handle_ad_res(self, request): try: body = {"code": 0, "data": [], "message": "success", "success": True} @@ -34,6 +30,3 @@ class portal_api_ecms(plugins.ConfServerApp): except Exception as e: logging.exception(f"{e}") - - -plugin = portal_api_ecms() diff --git a/bumper/plugins/bumper_confserver_portal_iot.py b/bumper/plugins/bumper_confserver_portal_iot.py index 330d852..d3392e0 100644 --- a/bumper/plugins/bumper_confserver_portal_iot.py +++ b/bumper/plugins/bumper_confserver_portal_iot.py @@ -27,10 +27,6 @@ class portal_api_iot(plugins.ConfServerApp): ), ] - self.get_milli_time = ( - bumper.ConfServer.ConfServer_GeneralFunctions().get_milli_time - ) - async def handle_devmanager_botcommand(self, request): try: json_body = json.loads(await request.text()) @@ -81,6 +77,3 @@ class portal_api_iot(plugins.ConfServerApp): except Exception as e: logging.exception(f"{e}") - - -plugin = portal_api_iot() diff --git a/bumper/plugins/bumper_confserver_portal_lg.py b/bumper/plugins/bumper_confserver_portal_lg.py index 779d44e..5ffbff8 100644 --- a/bumper/plugins/bumper_confserver_portal_lg.py +++ b/bumper/plugins/bumper_confserver_portal_lg.py @@ -20,10 +20,6 @@ class portal_api_lg(plugins.ConfServerApp): web.route("*", "/lg/log.do", self.handle_lg_log, name="portal_api_lg_log"), ] - self.get_milli_time = ( - bumper.ConfServer.ConfServer_GeneralFunctions().get_milli_time - ) - async def handle_lg_log(self, request): # EcoVacs Home randomid = "".join(random.sample(string.ascii_letters, 6)) @@ -101,6 +97,3 @@ class portal_api_lg(plugins.ConfServerApp): body = {"id": randomid, "errno": bumper.ERR_COMMON, "ret": "fail"} return web.json_response(body) - - -plugin = portal_api_lg() diff --git a/bumper/plugins/bumper_confserver_portal_neng.py b/bumper/plugins/bumper_confserver_portal_neng.py index 7ffb7a7..a017ea0 100644 --- a/bumper/plugins/bumper_confserver_portal_neng.py +++ b/bumper/plugins/bumper_confserver_portal_neng.py @@ -37,10 +37,6 @@ class portal_api_neng(plugins.ConfServerApp): ), ] - self.get_milli_time = ( - bumper.ConfServer.ConfServer_GeneralFunctions().get_milli_time - ) - async def handle_neng_hasUnreadMessage(self, request): # EcoVacs Home try: body = {"code": 0, "data": {"hasUnRead": True}} @@ -107,6 +103,3 @@ class portal_api_neng(plugins.ConfServerApp): except Exception as e: logging.exception(f"{e}") - - -plugin = portal_api_neng() diff --git a/bumper/plugins/bumper_confserver_portal_pim.py b/bumper/plugins/bumper_confserver_portal_pim.py index 287d727..7ec2031 100644 --- a/bumper/plugins/bumper_confserver_portal_pim.py +++ b/bumper/plugins/bumper_confserver_portal_pim.py @@ -53,10 +53,6 @@ class portal_api_pim(plugins.ConfServerApp): ), ] - self.get_milli_time = ( - bumper.ConfServer.ConfServer_GeneralFunctions().get_milli_time - ) - async def handle_getProductIotMap(self, request): try: body = { @@ -130,8 +126,6 @@ class portal_api_pim(plugins.ConfServerApp): logging.exception(f"{e}") -plugin = portal_api_pim() - confignetAllResponse = { "code": 0, "data": [ diff --git a/bumper/plugins/bumper_confserver_portal_rapp.py b/bumper/plugins/bumper_confserver_portal_rapp.py index 0b0ac3b..2331978 100644 --- a/bumper/plugins/bumper_confserver_portal_rapp.py +++ b/bumper/plugins/bumper_confserver_portal_rapp.py @@ -19,10 +19,6 @@ class api_rapp(plugins.ConfServerApp): ), ] - self.get_milli_time = ( - bumper.ConfServer.ConfServer_GeneralFunctions().get_milli_time - ) - async def handle_map_get(self, request): try: body = { @@ -35,6 +31,3 @@ class api_rapp(plugins.ConfServerApp): except Exception as e: logging.exception(f"{e}") - - -plugin = api_rapp() diff --git a/bumper/plugins/bumper_confserver_portal_users.py b/bumper/plugins/bumper_confserver_portal_users.py index 3118abc..165acb4 100644 --- a/bumper/plugins/bumper_confserver_portal_users.py +++ b/bumper/plugins/bumper_confserver_portal_users.py @@ -25,10 +25,6 @@ class portal_api_users(plugins.ConfServerApp): ), ] - self.get_milli_time = ( - bumper.ConfServer.ConfServer_GeneralFunctions().get_milli_time - ) - async def handle_usersapi(self, request): if not request.method == "GET": # Skip GET for now try: @@ -120,6 +116,3 @@ class portal_api_users(plugins.ConfServerApp): # Return fail for GET body = {"result": "fail", "todo": "result"} return web.json_response(body) - - -plugin = portal_api_users() diff --git a/bumper/plugins/bumper_confserver_upload_global.py b/bumper/plugins/bumper_confserver_upload_global.py index 5ab1aae..dba234e 100644 --- a/bumper/plugins/bumper_confserver_upload_global.py +++ b/bumper/plugins/bumper_confserver_upload_global.py @@ -26,10 +26,6 @@ class upload_global(plugins.ConfServerApp): ), ] - self.get_milli_time = ( - bumper.ConfServer.ConfServer_GeneralFunctions().get_milli_time - ) - async def handle_upload_global_file(self, request): try: fileID = request.match_info.get("id", "") @@ -42,6 +38,3 @@ class upload_global(plugins.ConfServerApp): except Exception as e: logging.exception(f"{e}") - - -plugin = upload_global() diff --git a/bumper/plugins/bumper_confserver_v1_global_auth.py b/bumper/plugins/bumper_confserver_v1_global_auth.py index 3650647..3ef26bf 100644 --- a/bumper/plugins/bumper_confserver_v1_global_auth.py +++ b/bumper/plugins/bumper_confserver_v1_global_auth.py @@ -8,6 +8,7 @@ from aiohttp import web import bumper from bumper import plugins from bumper.models import * +from bumper.rest import auth_util class v1_global_auth(plugins.ConfServerApp): @@ -16,19 +17,11 @@ class v1_global_auth(plugins.ConfServerApp): self.plugin_type = "sub_api" self.sub_api = "api_v1" - authhandler = bumper.ConfServer.ConfServer_AuthHandler() self.routes = [ web.route( "*", "/global/auth/getAuthCode", - authhandler.get_AuthCode, + auth_util.get_authcode, name="v1_global_auth_getAuthCode", ), ] - - self.get_milli_time = ( - bumper.ConfServer.ConfServer_GeneralFunctions().get_milli_time - ) - - -plugin = v1_global_auth() diff --git a/bumper/plugins/bumper_confserver_v1_private_ad.py b/bumper/plugins/bumper_confserver_v1_private_ad.py index 36869e3..f7808d8 100644 --- a/bumper/plugins/bumper_confserver_v1_private_ad.py +++ b/bumper/plugins/bumper_confserver_v1_private_ad.py @@ -8,6 +8,7 @@ from aiohttp import web import bumper from bumper import plugins from bumper.models import * +from bumper.util import get_current_time_as_millis class v1_private_ad(plugins.ConfServerApp): @@ -31,10 +32,6 @@ class v1_private_ad(plugins.ConfServerApp): ), ] - self.get_milli_time = ( - bumper.ConfServer.ConfServer_GeneralFunctions().get_milli_time - ) - async def handle_getAdByPositionType(self, request): # EcoVacs Home try: body = { @@ -42,7 +39,7 @@ class v1_private_ad(plugins.ConfServerApp): "data": None, "msg": "操作成功", "success": True, - "time": self.get_milli_time(datetime.utcnow().timestamp()), + "time": get_current_time_as_millis(), } return web.json_response(body) @@ -57,13 +54,10 @@ class v1_private_ad(plugins.ConfServerApp): "data": None, "msg": "操作成功", "success": True, - "time": self.get_milli_time(datetime.utcnow().timestamp()), + "time": get_current_time_as_millis(), } return web.json_response(body) except Exception as e: logging.exception(f"{e}") - - -plugin = v1_private_ad() diff --git a/bumper/plugins/bumper_confserver_v1_private_campaign.py b/bumper/plugins/bumper_confserver_v1_private_campaign.py index d0ce35d..bf3f1b5 100644 --- a/bumper/plugins/bumper_confserver_v1_private_campaign.py +++ b/bumper/plugins/bumper_confserver_v1_private_campaign.py @@ -8,6 +8,7 @@ from aiohttp import web import bumper from bumper import plugins from bumper.models import * +from bumper.util import get_current_time_as_millis class v1_private_campaign(plugins.ConfServerApp): @@ -25,13 +26,9 @@ class v1_private_campaign(plugins.ConfServerApp): ), ] - self.get_milli_time = ( - bumper.ConfServer.ConfServer_GeneralFunctions().get_milli_time - ) - async def handle_homePageAlert(self, request): try: - nextAlert = self.get_milli_time( + nextAlert = convert_to_millis( (datetime.now() + timedelta(hours=12)).timestamp() ) @@ -43,16 +40,13 @@ class v1_private_campaign(plugins.ConfServerApp): "hasCampaign": "N", "imageUrl": None, "nextAlertTime": nextAlert, - "serverTime": self.get_milli_time(datetime.utcnow().timestamp()), + "serverTime": get_current_time_as_millis(), }, "msg": "操作成功", - "time": self.get_milli_time(datetime.utcnow().timestamp()), + "time": get_current_time_as_millis(), } return web.json_response(body) except Exception as e: logging.exception(f"{e}") - - -plugin = v1_private_campaign() diff --git a/bumper/plugins/bumper_confserver_v1_private_common.py b/bumper/plugins/bumper_confserver_v1_private_common.py index 9727828..9b3a626 100644 --- a/bumper/plugins/bumper_confserver_v1_private_common.py +++ b/bumper/plugins/bumper_confserver_v1_private_common.py @@ -5,6 +5,7 @@ from aiohttp import web from bumper import plugins from bumper.models import * +from bumper.util import get_current_time_as_millis class v1_private_common(plugins.ConfServerApp): @@ -64,10 +65,6 @@ class v1_private_common(plugins.ConfServerApp): ), ] - self.get_milli_time = ( - bumper.ConfServer.ConfServer_GeneralFunctions().get_milli_time - ) - async def handle_checkVersion(self, request): try: body = { @@ -82,7 +79,7 @@ class v1_private_common(plugins.ConfServerApp): "v": None, }, "msg": "操作成功", - "time": self.get_milli_time(datetime.utcnow().timestamp()), + "time": get_current_time_as_millis(), } return web.json_response(body) @@ -107,7 +104,7 @@ class v1_private_common(plugins.ConfServerApp): }, "msg": "操作成功", "success": True, - "time": self.get_milli_time(datetime.utcnow().timestamp()), + "time": get_current_time_as_millis(), } return web.json_response(body) @@ -122,7 +119,7 @@ class v1_private_common(plugins.ConfServerApp): "data": None, "msg": "操作成功", "success": True, - "time": self.get_milli_time(datetime.utcnow().timestamp()), + "time": get_current_time_as_millis(), } return web.json_response(body) @@ -144,7 +141,7 @@ class v1_private_common(plugins.ConfServerApp): }, "msg": "操作成功", "success": True, - "time": self.get_milli_time(datetime.utcnow().timestamp()), + "time": get_current_time_as_millis(), } return web.json_response(body) @@ -163,7 +160,7 @@ class v1_private_common(plugins.ConfServerApp): "data": data, "msg": "操作成功", "success": True, - "time": self.get_milli_time(datetime.utcnow().timestamp()), + "time": get_current_time_as_millis(), } return web.json_response(body) @@ -178,7 +175,7 @@ class v1_private_common(plugins.ConfServerApp): "data": AREA_LIST, "msg": "操作成功", "success": True, - "time": self.get_milli_time(datetime.utcnow().timestamp()), + "time": get_current_time_as_millis(), } return web.json_response(body) @@ -210,7 +207,7 @@ class v1_private_common(plugins.ConfServerApp): ], "msg": "操作成功", "success": True, - "time": self.get_milli_time(datetime.utcnow().timestamp()), + "time": get_current_time_as_millis(), } return web.json_response(body) @@ -220,7 +217,7 @@ class v1_private_common(plugins.ConfServerApp): async def handle_getTimestamp(self, request): # EcoVacs Home try: - time = self.get_milli_time(datetime.utcnow().timestamp()) + time = get_current_time_as_millis() body = { "code": bumper.RETURN_API_SUCCESS, "data": {"timestamp": time}, @@ -235,8 +232,6 @@ class v1_private_common(plugins.ConfServerApp): logging.exception(f"{e}") -plugin = v1_private_common() - AREA_LIST = { "currentVersion": 231, "areaList": [ diff --git a/bumper/plugins/bumper_confserver_v1_private_message.py b/bumper/plugins/bumper_confserver_v1_private_message.py index 6bb4eac..56cea47 100644 --- a/bumper/plugins/bumper_confserver_v1_private_message.py +++ b/bumper/plugins/bumper_confserver_v1_private_message.py @@ -8,6 +8,7 @@ from aiohttp import web import bumper from bumper import plugins from bumper.models import * +from bumper.util import get_current_time_as_millis class v1_private_message(plugins.ConfServerApp): @@ -31,10 +32,6 @@ class v1_private_message(plugins.ConfServerApp): ), ] - self.get_milli_time = ( - bumper.ConfServer.ConfServer_GeneralFunctions().get_milli_time - ) - async def handle_hasUnreadMessage(self, request): # EcoVacs Home try: body = { @@ -42,7 +39,7 @@ class v1_private_message(plugins.ConfServerApp): "data": "N", "msg": "操作成功", "success": True, - "time": self.get_milli_time(datetime.utcnow().timestamp()), + "time": get_current_time_as_millis(), } return web.json_response(body) @@ -57,13 +54,10 @@ class v1_private_message(plugins.ConfServerApp): "data": {"hasNextPage": 0, "items": []}, "msg": "操作成功", "success": True, - "time": self.get_milli_time(datetime.utcnow().timestamp()), + "time": get_current_time_as_millis(), } return web.json_response(body) except Exception as e: logging.exception(f"{e}") - - -plugin = v1_private_message() diff --git a/bumper/plugins/bumper_confserver_v1_private_shop.py b/bumper/plugins/bumper_confserver_v1_private_shop.py index 00693d0..650365a 100644 --- a/bumper/plugins/bumper_confserver_v1_private_shop.py +++ b/bumper/plugins/bumper_confserver_v1_private_shop.py @@ -8,6 +8,7 @@ from aiohttp import web import bumper from bumper import plugins from bumper.models import * +from bumper.util import get_current_time_as_millis class v1_private_shop(plugins.ConfServerApp): @@ -25,10 +26,6 @@ class v1_private_shop(plugins.ConfServerApp): ), ] - self.get_milli_time = ( - bumper.ConfServer.ConfServer_GeneralFunctions().get_milli_time - ) - async def handle_getCnWapShopConfig(self, request): # EcoVacs Home try: body = { @@ -41,13 +38,10 @@ class v1_private_shop(plugins.ConfServerApp): }, "msg": "操作成功", "success": True, - "time": self.get_milli_time(datetime.utcnow().timestamp()), + "time": get_current_time_as_millis(), } return web.json_response(body) except Exception as e: logging.exception(f"{e}") - - -plugin = v1_private_shop() diff --git a/bumper/plugins/bumper_confserver_v1_private_user.py b/bumper/plugins/bumper_confserver_v1_private_user.py index f4c5cce..982154f 100644 --- a/bumper/plugins/bumper_confserver_v1_private_user.py +++ b/bumper/plugins/bumper_confserver_v1_private_user.py @@ -8,6 +8,8 @@ from aiohttp import web import bumper from bumper import plugins from bumper.models import * +from bumper.rest import auth_util +from bumper.util import get_current_time_as_millis class v1_private_user(plugins.ConfServerApp): @@ -17,30 +19,29 @@ class v1_private_user(plugins.ConfServerApp): self.plugin_type = "sub_api" self.sub_api = "api_v1" - authhandler = bumper.ConfServer.ConfServer_AuthHandler() self.routes = [ web.route( "*", "/private/{country}/{language}/{devid}/{apptype}/{appversion}/{devtype}/{aid}/user/login", - authhandler.login, + auth_util.login, name="v1_user_login", ), web.route( "*", "/private/{country}/{language}/{devid}/{apptype}/{appversion}/{devtype}/{aid}/user/checkLogin", - authhandler.login, + auth_util.login, name="v1_user_checkLogin", ), web.route( "*", "/private/{country}/{language}/{devid}/{apptype}/{appversion}/{devtype}/{aid}/user/getAuthCode", - authhandler.get_AuthCode, + auth_util.get_authcode, name="v1_user_getAuthCode", ), web.route( "*", "/private/{country}/{language}/{devid}/{apptype}/{appversion}/{devtype}/{aid}/user/logout", - authhandler.logout, + auth_util.logout, name="v1_user_logout", ), web.route( @@ -58,7 +59,7 @@ class v1_private_user(plugins.ConfServerApp): web.route( "*", "/private/{country}/{language}/{devid}/{apptype}/{appversion}/{devtype}/{aid}/user/getUserAccountInfo", - authhandler.getUserAccountInfo, + auth_util.get_user_account_info, name="v1_user_getUserAccountInfo", ), web.route( @@ -91,10 +92,6 @@ class v1_private_user(plugins.ConfServerApp): # /registerByEmail ] - self.get_milli_time = ( - bumper.ConfServer.ConfServer_GeneralFunctions().get_milli_time - ) - async def handle_checkAgreement(self, request): try: apptype = request.match_info.get("apptype", "") @@ -119,14 +116,14 @@ class v1_private_user(plugins.ConfServerApp): ], "msg": "操作成功", "success": True, - "time": self.get_milli_time(datetime.utcnow().timestamp()), + "time": get_current_time_as_millis(), } else: body = { "code": bumper.RETURN_API_SUCCESS, "data": [], "msg": "操作成功", - "time": self.get_milli_time(datetime.utcnow().timestamp()), + "time": get_current_time_as_millis(), } return web.json_response(body) @@ -195,7 +192,7 @@ class v1_private_user(plugins.ConfServerApp): ], "msg": "操作成功", "success": True, - "time": self.get_milli_time(datetime.utcnow().timestamp()), + "time": get_current_time_as_millis(), } return web.json_response(body) @@ -210,7 +207,7 @@ class v1_private_user(plugins.ConfServerApp): "data": {"isNeedReLogin": "N"}, "msg": "操作成功", "success": True, - "time": self.get_milli_time(datetime.utcnow().timestamp()), + "time": get_current_time_as_millis(), } return web.json_response(body) @@ -225,13 +222,10 @@ class v1_private_user(plugins.ConfServerApp): "data": None, "msg": "操作成功", "success": True, - "time": self.get_milli_time(datetime.utcnow().timestamp()), + "time": get_current_time_as_millis(), } return web.json_response(body) except Exception as e: logging.exception(f"{e}") - - -plugin = v1_private_user() diff --git a/bumper/plugins/bumper_confserver_v1_private_userSetting.py b/bumper/plugins/bumper_confserver_v1_private_userSetting.py index 37bb39d..d0f39fb 100644 --- a/bumper/plugins/bumper_confserver_v1_private_userSetting.py +++ b/bumper/plugins/bumper_confserver_v1_private_userSetting.py @@ -1,11 +1,11 @@ #!/usr/bin/env python3 import logging -from datetime import datetime from aiohttp import web import bumper from bumper import plugins +from bumper.util import get_current_time_as_millis class v1_private_userSetting(plugins.ConfServerApp): @@ -24,10 +24,6 @@ class v1_private_userSetting(plugins.ConfServerApp): ), ] - self.get_milli_time = ( - bumper.ConfServer.ConfServer_GeneralFunctions().get_milli_time - ) - async def handle_getSuggestionSetting(self, request): try: @@ -54,13 +50,10 @@ class v1_private_userSetting(plugins.ConfServerApp): ], }, "msg": "操作成功", - "time": self.get_milli_time(datetime.utcnow().timestamp()), + "time": get_current_time_as_millis(), } return web.json_response(body) except Exception as e: logging.exception(f"{e}") - - -plugin = v1_private_userSetting() diff --git a/bumper/plugins/bumper_confserver_v2_private_user.py b/bumper/plugins/bumper_confserver_v2_private_user.py index f076c7c..54e5d03 100644 --- a/bumper/plugins/bumper_confserver_v2_private_user.py +++ b/bumper/plugins/bumper_confserver_v2_private_user.py @@ -1,13 +1,8 @@ #!/usr/bin/env python3 -import asyncio -import logging -from datetime import datetime, timedelta - from aiohttp import web -import bumper from bumper import plugins -from bumper.models import * +from bumper.rest import auth_util class v2_private_user(plugins.ConfServerApp): @@ -17,19 +12,11 @@ class v2_private_user(plugins.ConfServerApp): self.plugin_type = "sub_api" self.sub_api = "api_v2" - authhandler = bumper.ConfServer.ConfServer_AuthHandler() self.routes = [ web.route( "*", "/private/{country}/{language}/{devid}/{apptype}/{appversion}/{devtype}/{aid}/user/checkLogin", - authhandler.login, + auth_util.login, name="v2_user_checkLogin", ), ] - - self.get_milli_time = ( - bumper.ConfServer.ConfServer_GeneralFunctions().get_milli_time - ) - - -plugin = v2_private_user() diff --git a/bumper/rest/__init__.py b/bumper/rest/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/bumper/rest/auth_util.py b/bumper/rest/auth_util.py new file mode 100644 index 0000000..334fa48 --- /dev/null +++ b/bumper/rest/auth_util.py @@ -0,0 +1,384 @@ +"""Auth util module.""" +import json +import uuid + +from aiohttp import web + +import bumper +from bumper import ( + get_logger, + EcoVacsHome_Login, + EcoVacs_Login, + API_ERRORS, + RETURN_API_SUCCESS, +) +from bumper.util import get_current_time_as_millis + +_logger = get_logger("confserver") + + +def generate_token(user): + try: + tmpaccesstoken = uuid.uuid4().hex + bumper.user_add_token(user["userid"], tmpaccesstoken) + return tmpaccesstoken + + except Exception as e: + _logger.exception(f"{e}") + + +def generate_authcode(user, countrycode, token): + try: + tmpauthcode = f"{countrycode}_{uuid.uuid4().hex}" + bumper.user_add_authcode(user["userid"], token, tmpauthcode) + return tmpauthcode + + except Exception as e: + _logger.exception(f"{e}") + + +async def login(request): + try: + user_devid = request.match_info.get("devid", "") + countrycode = request.match_info.get("country", "us") + apptype = request.match_info.get("apptype", "") + _logger.info(f"client with devid {user_devid} attempting login") + if bumper.use_auth: + if ( + not user_devid == "" + ): # Performing basic "auth" using devid, super insecure + user = bumper.user_by_deviceid(user_devid) + if "checkLogin" in request.path: + check_token( + apptype, countrycode, user, request.query["accessToken"] + ) + else: + if "global_" in apptype: # EcoVacs Home + login_details = EcoVacsHome_Login() + login_details.ucUid = "fuid_{}".format(user["userid"]) + login_details.loginName = "fusername_{}".format(user["userid"]) + login_details.mobile = None + + else: + login_details = EcoVacs_Login() + + # Deactivate old tokens and authcodes + bumper.user_revoke_expired_tokens(user["userid"]) + + login_details.accessToken = generate_token(user) + login_details.uid = "fuid_{}".format(user["userid"]) + login_details.username = "fusername_{}".format(user["userid"]) + login_details.country = countrycode + login_details.email = "null@null.com" + + body = { + "code": API_ERRORS[RETURN_API_SUCCESS], + "data": json.loads(login_details.toJSON()), + # { + # "accessToken": self.generate_token(tmpuser), # Generate a token + # "country": countrycode, + # "email": "null@null.com", + # "uid": "fuid_{}".format(tmpuser["userid"]), + # "username": "fusername_{}".format(tmpuser["userid"]), + # }, + "msg": "操作成功", + "time": get_current_time_as_millis(), + } + + return web.json_response(body) + + body = { + "code": bumper.ERR_USER_NOT_ACTIVATED, + "data": None, + "msg": "当前密码错误", + "time": get_current_time_as_millis(), + } + + return web.json_response(body) + + else: + return web.json_response( + _auth_any(user_devid, apptype, countrycode, request) + ) + + except Exception as e: + _logger.exception(f"{e}") + + +async def get_authcode(request): + try: + apptype = request.match_info.get("apptype", "") + user_devid = request.match_info.get("devid", "") # Ecovacs + if user_devid == "": + user_devid = request.query["deviceId"] # Ecovacs Home + + if not user_devid == "": + user = bumper.user_by_deviceid(user_devid) + token = "" + if user: + if "accessToken" in request.query: + token = bumper.user_get_token( + user["userid"], request.query["accessToken"] + ) + if token: + authcode = "" + if not "authcode" in token: + authcode = generate_authcode( + user, + request.match_info.get("country", "us"), + request.query["accessToken"], + ) + else: + authcode = token["authcode"] + if "global" in apptype: + body = { + "code": bumper.RETURN_API_SUCCESS, + "data": { + "authCode": authcode, + "ecovacsUid": request.query["uid"], + }, + "msg": "操作成功", + "success": True, + "time": get_current_time_as_millis(), + } + else: + body = { + "code": bumper.RETURN_API_SUCCESS, + "data": { + "authCode": authcode, + "ecovacsUid": request.query["uid"], + }, + "msg": "操作成功", + "time": get_current_time_as_millis(), + } + return web.json_response(body) + + body = { + "code": bumper.ERR_TOKEN_INVALID, + "data": None, + "msg": "当前密码错误", + "time": get_current_time_as_millis(), + } + + return web.json_response(body) + + except Exception as e: + _logger.exception(f"{e}") + + +def check_token(apptype, countrycode, user, token): + try: + if bumper.check_token(user["userid"], token): + + if "global_" in apptype: # EcoVacs Home + login_details = EcoVacsHome_Login() + login_details.ucUid = "fuid_{}".format(user["userid"]) + login_details.loginName = "fusername_{}".format(user["userid"]) + login_details.mobile = None + else: + login_details = EcoVacs_Login() + + login_details.accessToken = token + login_details.uid = "fuid_{}".format(user["userid"]) + login_details.username = "fusername_{}".format(user["userid"]) + login_details.country = countrycode + login_details.email = "null@null.com" + + body = { + "code": bumper.RETURN_API_SUCCESS, + "data": json.loads(login_details.toJSON()), + # { + # "accessToken": self.generate_token(tmpuser), # Generate a token + # "country": countrycode, + # "email": "null@null.com", + # "uid": "fuid_{}".format(tmpuser["userid"]), + # "username": "fusername_{}".format(tmpuser["userid"]), + # }, + "msg": "操作成功", + "time": get_current_time_as_millis(), + } + return web.json_response(body) + + else: + body = { + "code": bumper.ERR_TOKEN_INVALID, + "data": None, + "msg": "当前密码错误", + "time": get_current_time_as_millis(), + } + return web.json_response(body) + + except Exception as e: + _logger.exception(f"{e}") + + +def _auth_any(devid, apptype, country, request): + try: + user_devid = devid + countrycode = country + user = bumper.user_by_deviceid(user_devid) + bots = bumper.db_get().table("bots").all() + + if user: # Default to user 0 + tmpuser = user + if "global_" in apptype: # EcoVacs Home + login_details = EcoVacsHome_Login() + login_details.ucUid = "fuid_{}".format(tmpuser["userid"]) + login_details.loginName = "fusername_{}".format(tmpuser["userid"]) + login_details.mobile = None + else: + login_details = EcoVacs_Login() + + login_details.accessToken = generate_token(tmpuser) + login_details.uid = "fuid_{}".format(tmpuser["userid"]) + login_details.username = "fusername_{}".format(tmpuser["userid"]) + login_details.country = countrycode + login_details.email = "null@null.com" + bumper.user_add_device(tmpuser["userid"], user_devid) + else: + bumper.user_add("tmpuser") # Add a new user + tmpuser = bumper.user_get("tmpuser") + if "global_" in apptype: # EcoVacs Home + login_details = EcoVacsHome_Login() + login_details.ucUid = "fuid_{}".format(tmpuser["userid"]) + login_details.loginName = "fusername_{}".format(tmpuser["userid"]) + login_details.mobile = None + else: + login_details = EcoVacs_Login() + + login_details.accessToken = generate_token(tmpuser) + login_details.uid = "fuid_{}".format(tmpuser["userid"]) + login_details.username = "fusername_{}".format(tmpuser["userid"]) + login_details.country = countrycode + login_details.email = "null@null.com" + bumper.user_add_device(tmpuser["userid"], user_devid) + + for bot in bots: # Add all bots to the user + if "did" in bot: + bumper.user_add_bot(tmpuser["userid"], bot["did"]) + else: + _logger.error(f"No DID for bot: {bot}") + + if "checkLogin" in request.path: # If request was to check a token do so + checkToken = check_token( + apptype, countrycode, tmpuser, request.query["accessToken"] + ) + isGood = json.loads(checkToken.text) + if isGood["code"] == "0000": + return isGood + + # Deactivate old tokens and authcodes + bumper.user_revoke_expired_tokens(tmpuser["userid"]) + + body = { + "code": bumper.RETURN_API_SUCCESS, + "data": json.loads(login_details.toJSON()), + # { + # "accessToken": self.generate_token(tmpuser), # Generate a token + # "country": countrycode, + # "email": "null@null.com", + # "uid": "fuid_{}".format(tmpuser["userid"]), + # "username": "fusername_{}".format(tmpuser["userid"]), + # }, + "msg": "操作成功", + "time": get_current_time_as_millis(), + } + + return body + + except Exception as e: + _logger.exception(f"{e}") + + +def get_user_account_info(request): + try: + user_devid = request.match_info.get("devid", "") + countrycode = request.match_info.get("country", "us") + apptype = request.match_info.get("apptype", "") + user = bumper.user_by_deviceid(user_devid) + + if "global_" in apptype: # EcoVacs Home + login_details = EcoVacsHome_Login() + login_details.ucUid = "fuid_{}".format(user["userid"]) + login_details.loginName = "fusername_{}".format(user["userid"]) + login_details.mobile = None + else: + login_details = EcoVacs_Login() + + login_details.uid = "fuid_{}".format(user["userid"]) + login_details.username = "fusername_{}".format(user["userid"]) + login_details.country = countrycode + login_details.email = "null@null.com" + + body = { + "code": bumper.RETURN_API_SUCCESS, + "data": { + "email": login_details.email, + "hasMobile": "N", + "hasPassword": "Y", + "uid": login_details.uid, + "userName": login_details.username, + "obfuscatedMobile": None, + "mobile": None, + "loginName": login_details.loginName, + }, + "msg": "操作成功", + "time": get_current_time_as_millis(), + } + + # Example body + # { + # "code": "0000", + # "data": { + # "email": "user@gmail.com", + # "hasMobile": "N", + # "hasPassword": "Y", + # "headIco": "", + # "loginName": "user@gmail.com", + # "mobile": null, + # "mobileAreaNo": null, + # "nickname": "", + # "obfuscatedMobile": null, + # "thirdLoginInfoList": [ + # { + # "accountType": "WeChat", + # "hasBind": "N" + # } + # ], + # "uid": "20180719212155_*****", + # "userName": "EAY*****" + # }, + # "msg": "操作成功", + # "success": true, + # "time": 1578203898343 + # } + + return web.json_response(body) + + except Exception as e: + _logger.exception(f"{e}") + + +async def logout(request): + try: + user_devid = request.match_info.get("devid", "") + if not user_devid == "": + user = bumper.user_by_deviceid(user_devid) + if user: + if bumper.check_token(user["userid"], request.query["accessToken"]): + # Deactivate old tokens and authcodes + bumper.user_revoke_token( + user["userid"], request.query["accessToken"] + ) + + body = { + "code": bumper.RETURN_API_SUCCESS, + "data": None, + "msg": "操作成功", + "time": get_current_time_as_millis(), + } + + return web.json_response(body) + + except Exception as e: + _logger.exception(f"{e}") diff --git a/bumper/util.py b/bumper/util.py index 5d9c0c5..400a09d 100644 --- a/bumper/util.py +++ b/bumper/util.py @@ -1,8 +1,9 @@ import logging import os import sys +from datetime import datetime from logging.handlers import RotatingFileHandler -from typing import MutableMapping +from typing import MutableMapping, Union logformat = logging.Formatter( "[%(asctime)s] :: %(levelname)s :: %(name)s :: %(module)s :: %(funcName)s :: %(lineno)d :: %(message)s" @@ -43,3 +44,12 @@ def get_logger(name: str, rotate: RotatingFileHandler = None) -> logging.Logger: get_logger("amqtt.client", rotate) return logger + + +def convert_to_millis(seconds: Union[int, float]) -> int: + """Convert seconds to milliseconds.""" + return int(round(seconds * 1000)) + + +def get_current_time_as_millis() -> int: + return convert_to_millis(datetime.utcnow().timestamp()) diff --git a/bumper/xmppserver.py b/bumper/xmppserver.py index 831d360..f98c72a 100644 --- a/bumper/xmppserver.py +++ b/bumper/xmppserver.py @@ -26,7 +26,7 @@ class XMPPServer: self.address = address self.xmpp_protocol = lambda: XMPPServer_Protocol() - async def start_async_server(self): + async def start_async_server(self) -> None: try: xmppserverlog.info( f"Starting XMPP Server at {self.address[0]}:{self.address[1]}" @@ -44,7 +44,7 @@ class XMPPServer: xmppserverlog.exception(f"{e}") raise e - def disconnect(self): + def disconnect(self) -> None: xmppserverlog.debug("waiting for all clients to disconnect") for client in self.clients: diff --git a/tests/__init__.py b/tests/__init__.py index 143d88a..45a96c9 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -1,2 +1,3 @@ HOST = "127.0.0.1" MQTT_PORT = 8883 +CONF_SERVER_PORT = 11111 diff --git a/tests/conftest.py b/tests/conftest.py index dc6ee31..21076c4 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -4,7 +4,8 @@ import pytest from amqtt.client import MQTTClient import bumper -from tests import HOST, MQTT_PORT +from bumper import WebServerBinding +from tests import HOST, MQTT_PORT, CONF_SERVER_PORT @pytest.fixture @@ -37,10 +38,8 @@ async def mqtt_client(): @pytest.fixture async def conf_server_client(aiohttp_client): - confserver = bumper.ConfServer("127.0.0.1:11111", False) - confserver.confserver_app() - - client = await aiohttp_client(confserver.app) + confserver = bumper.ConfServer(WebServerBinding(HOST, CONF_SERVER_PORT, False)) + client = await aiohttp_client(confserver._app) yield client diff --git a/tests/test_confserver.py b/tests/test_confserver.py index dfcaaff..cc76b2b 100644 --- a/tests/test_confserver.py +++ b/tests/test_confserver.py @@ -9,11 +9,12 @@ from aiohttp import web from testfixtures import LogCapture import bumper -from tests import HOST, MQTT_PORT +from bumper import WebServerBinding +from tests import HOST, MQTT_PORT, CONF_SERVER_PORT def create_confserver(): - return bumper.ConfServer("127.0.0.1:11111", False) + return bumper.ConfServer(WebServerBinding(HOST, CONF_SERVER_PORT, False)) def async_return(result): @@ -28,27 +29,13 @@ def remove_existing_db(): async def test_confserver_ssl(): - conf_server = bumper.ConfServer((HOST, 11111), usessl=True) - conf_server.confserver_app() - await conf_server.start_server() + conf_server = bumper.ConfServer(WebServerBinding(HOST, CONF_SERVER_PORT, True)) + await conf_server.start() async def test_confserver_no_ssl(): - conf_server = bumper.ConfServer((HOST, 11112), usessl=False) - conf_server.confserver_app() - await conf_server.start_server() - - -def test_get_milli_time(): - cserv = create_confserver() - assert ( - cserv.get_milli_time( - datetime.datetime( - 2018, 1, 1, 1, 0, 0, 0, tzinfo=datetime.timezone.utc - ).timestamp() - ) - == 1514768400000 - ) + conf_server = bumper.ConfServer(WebServerBinding(HOST, 11112, False)) + await conf_server.start() @pytest.mark.usefixtures("mqtt_server") diff --git a/tests/test_init.py b/tests/test_init.py index a1badb2..f8a064c 100644 --- a/tests/test_init.py +++ b/tests/test_init.py @@ -26,15 +26,12 @@ async def test_start_stop(): b = bumper b.db = "tests/tmp.db" # Set db location for testing - b.conf1_listen_address = "127.0.0.1" - b.conf1_listen_port = 444 asyncio.create_task(b.start()) await asyncio.sleep(0.1) l.check_present(("bumper", "INFO", "Starting Bumper")) l.clear() - asyncio.create_task(b.shutdown()) - await asyncio.sleep(0.1) + await b.shutdown() l.check_present( ("bumper", "INFO", "Shutting down"), ("bumper", "INFO", "Shutdown complete") ) diff --git a/tests/test_util.py b/tests/test_util.py new file mode 100644 index 0000000..c281599 --- /dev/null +++ b/tests/test_util.py @@ -0,0 +1,14 @@ +import datetime + +from bumper.util import convert_to_millis + + +def test_get_milli_time(): + assert ( + convert_to_millis( + datetime.datetime( + 2018, 1, 1, 1, 0, 0, 0, tzinfo=datetime.timezone.utc + ).timestamp() + ) + == 1514768400000 + )