diff --git a/Pipfile b/Pipfile index fb910e4..563961f 100644 --- a/Pipfile +++ b/Pipfile @@ -11,6 +11,8 @@ pyyaml = "~=5.2" atomicwrites = "~=1.3" yarl = "~=1.3.0" multidict = "~=4.5.2" +aiohttp-jinja2 = "*" +jinja2 = "*" [dev-packages] black = "*" diff --git a/Pipfile.lock b/Pipfile.lock index adf5518..0383d41 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "315b25b9f7d46a6ea88fb08170ad935b35aa2717743be4eca137e52e595066de" + "sha256": "740f61688ce679591a1b01f41abc1fa979f8fb1552d598e68868bc47712ba937" }, "pipfile-spec": 6, "requires": {}, @@ -32,6 +32,14 @@ "index": "pypi", "version": "==3.6.2" }, + "aiohttp-jinja2": { + "hashes": [ + "sha256:2dfe29cfd278d07cd0a851afb98471bc8ce2a830968443e40d67636f3c035d79", + "sha256:3b4dfe1bfd5542e254a769c18cb58d62f7f92755fec127e38d0da3436900b240" + ], + "index": "pypi", + "version": "==1.2.0" + }, "async-timeout": { "hashes": [ "sha256:0c3c816a028d47f659d6ff5c745cb2acf1f966da1fe5c19c77a70282b25f4c5f", @@ -82,6 +90,47 @@ ], "version": "==2.8" }, + "jinja2": { + "hashes": [ + "sha256:74320bb91f31270f9551d46522e33af46a80c3d619f4a4bf42b3164d30b5911f", + "sha256:9fe95f19286cfefaa917656583d020be14e7859c6b0252588391e47db34527de" + ], + "index": "pypi", + "version": "==2.10.3" + }, + "markupsafe": { + "hashes": [ + "sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473", + "sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161", + "sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235", + "sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5", + "sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff", + "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b", + "sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1", + "sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e", + "sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183", + "sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66", + "sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1", + "sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1", + "sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e", + "sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b", + "sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905", + "sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735", + "sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d", + "sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e", + "sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d", + "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c", + "sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21", + "sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2", + "sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5", + "sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b", + "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6", + "sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f", + "sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f", + "sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7" + ], + "version": "==1.1.1" + }, "multidict": { "hashes": [ "sha256:024b8129695a952ebd93373e45b5d341dbb87c17ce49637b34000093f243dd4f", @@ -636,10 +685,9 @@ }, "wcwidth": { "hashes": [ - "sha256:3df37372226d6e63e1b1e1eda15c594bca98a22d33a23832a90998faa96bc65e", - "sha256:f4ebe71925af7b40a864553f761ed559b43544f8f71746c2d756c7fe788ade7c" + "sha256:8fd29383f539be45b20bd4df0dc29c20ba48654a41e661925e612311e9f3c603" ], - "version": "==0.1.7" + "version": "==0.1.8" }, "wrapt": { "hashes": [ diff --git a/bumper/__init__.py b/bumper/__init__.py index 1ef871e..ff03f00 100644 --- a/bumper/__init__.py +++ b/bumper/__init__.py @@ -12,6 +12,9 @@ from logging.handlers import RotatingFileHandler import socket import sys +import importlib +import pkgutil +from pkgutil import extend_path def strtobool(strbool): if str(strbool).lower() in ["true", "1", "t", "y", "on", "yes"]: @@ -32,6 +35,8 @@ os.makedirs(data_dir, exist_ok=True) # Ensure data directory exists or create certs_dir = os.environ.get("BUMPER_CERTS") or os.path.join(bumper_dir, "certs") os.makedirs(certs_dir, exist_ok=True) # Ensure data directory exists or create + + # Certs ca_cert = os.environ.get("BUMPER_CA") or os.path.join(certs_dir, "ca.crt") server_cert = os.environ.get("BUMPER_CERT") or os.path.join(certs_dir, "bumper.crt") @@ -57,6 +62,16 @@ conf_server = None conf_server_2 = None xmpp_server = None +# Plugins +sys.path.append(os.path.join(bumper_dir, "bumper", "plugins")) +sys.path.append(os.path.join(data_dir, "plugins")) + +discovered_plugins = { + name: importlib.import_module(name) + for finder, name, ispkg in pkgutil.iter_modules() + if name.startswith('bumper_') +} + shutting_down = False # Set format for all logs @@ -206,10 +221,8 @@ async def start(): # Start web servers conf_server.confserver_app() - asyncio.create_task(conf_server.start_server()) - - conf_server_2.confserver_app() - asyncio.create_task(conf_server_2.start_server()) + 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)) # Start maintenance while not shutting_down: diff --git a/bumper/confserver.py b/bumper/confserver.py index af95f06..0e019f2 100644 --- a/bumper/confserver.py +++ b/bumper/confserver.py @@ -8,9 +8,12 @@ import random import bumper import os from bumper.models import * +from bumper import plugins from datetime import datetime, timedelta import asyncio from aiohttp import web +import aiohttp_jinja2 +import jinja2 import uuid import xml.etree.ElementTree as ET @@ -45,110 +48,85 @@ class ConfServer: self.app = None self.site = None self.runner = None + self.runners = [] + self.excludelogging = ["base", "remove-bot", "remove-client", "restart-service"] def get_milli_time(self, timetoconvert): return int(round(timetoconvert * 1000)) def confserver_app(self): - self.app = web.Application(loop=asyncio.get_event_loop(), middlewares=[self.log_all_requests]) + self.app = web.Application(loop=asyncio.get_event_loop(), middlewares=[ + self.log_all_requests, + ]) + aiohttp_jinja2.setup(self.app, loader=jinja2.FileSystemLoader(os.path.join(bumper.data_dir, "web","templates"))) self.app.add_routes( [ - web.get("", self.handle_base), - web.get("/restart_{service}", self.handle_RestartService, name='restart-service'), - web.get( - "/{apiversion}/private/{country}/{language}/{devid}/{apptype}/{appversion}/{devtype}/{aid}/user/login", - self.handle_login, - ), - web.get( - "/{apiversion}/private/{country}/{language}/{devid}/{apptype}/{appversion}/{devtype}/{aid}/user/checkLogin", - self.handle_login, - ), - web.get( # EcoVacs Home GetUserAccountInfo - "/{apiversion}/private/{country}/{language}/{devid}/{apptype}/{appversion}/{devtype}/{aid}/user/getUserAccountInfo", - self.handle_getUserAccountInfo, - ), - web.get( - "/{apiversion}/private/{country}/{language}/{devid}/{apptype}/{appversion}/{devtype}/{aid}/user/logout", - self.handle_logout, - ), - web.get( - "/{apiversion}/private/{country}/{language}/{devid}/{apptype}/{appversion}/{devtype}/{aid}/user/getAuthCode", - self.handle_getAuthCode, - ), - web.get( # EcoVacs Home GetAuthCode - "/{apiversion}/{apptype}/auth/getAuthCode", self.handle_getAuthCode - ), - web.get( - "/{apiversion}/private/{country}/{language}/{devid}/{apptype}/{appversion}/{devtype}/{aid}/user/checkAgreement", - self.handle_checkAgreement, - ), - web.get( # EcoVacs Home CheckAgreement - "/{apiversion}/private/{country}/{language}/{devid}/{apptype}/{appversion}/{devtype}/{aid}/user/checkAgreementBatch", - self.handle_checkAgreement, - ), - web.get( - "/{apiversion}/private/{country}/{language}/{devid}/{apptype}/{appversion}/{devtype}/{aid}/common/checkVersion", - self.handle_checkVersion, - ), - web.get( # EcoVacs Home CheckAPPVersion - "/{apiversion}/private/{country}/{language}/{devid}/{apptype}/{appversion}/{devtype}/{aid}/common/checkAPPVersion", - self.handle_checkAPPVersion, - ), - web.get( # EcoVacs Home Upload Device Info - "/{apiversion}/private/{country}/{language}/{devid}/{apptype}/{appversion}/{devtype}/{aid}/common/uploadDeviceInfo", - self.handle_uploadDeviceInfo, - ), - web.get( # EcoVacs Home GetAdByPositionType - "/{apiversion}/private/{country}/{language}/{devid}/{apptype}/{appversion}/{devtype}/{aid}/ad/getAdByPositionType", - self.handle_getAdByPositionType, - ), - web.get( # EcoVacs Home Get Boot Screen - "/{apiversion}/private/{country}/{language}/{devid}/{apptype}/{appversion}/{devtype}/{aid}/ad/getBootScreen", - self.handle_getBootScreen, - ), - web.get( # EcoVacs Home message hasUnreadMsg - "/{apiversion}/private/{country}/{language}/{devid}/{apptype}/{appversion}/{devtype}/{aid}/message/hasUnreadMsg", - self.handle_hasUnreadMessage, - ), - web.post( # EcoVacs Home neng message hasUnreadMsg - "/api/neng/message/hasUnreadMsg", self.handle_neng_hasUnreadMessage - ), - web.get( # EcoVacs Home message getMsgList - "/{apiversion}/private/{country}/{language}/{devid}/{apptype}/{appversion}/{devtype}/{aid}/message/getMsgList", - self.handle_getMsgList, - ), - web.get( # EcoVacs Home common getSystemReminder - "/{apiversion}/private/{country}/{language}/{devid}/{apptype}/{appversion}/{devtype}/{aid}/common/getSystemReminder", - self.handle_getSystemReminder, - ), - web.get( # EcoVacs Home shop getCnWapShopConfig - "/{apiversion}/private/{country}/{language}/{devid}/{apptype}/{appversion}/{devtype}/{aid}/shop/getCnWapShopConfig", - self.handle_getCnWapShopConfig, - ), - web.get( - "/{apiversion}/private/{country}/{language}/{devid}/{apptype}/{appversion}/{devtype}/{aid}/campaign/homePageAlert", - self.handle_homePageAlert, - ), - web.post("/api/users/user.do", self.handle_usersapi), - web.get("/api/users/user.do", self.handle_usersapi), - web.post("/api/appsvr/app.do", self.handle_appsvr_api), # EcoVacs Home - web.get("/api/appsvr/app.do", self.handle_appsvr_api), # EcoVacs Home - web.post( - "/api/pim/product/getProductIotMap", self.handle_getProductIotMap, name='getProductIotMap' - ), - web.post("/api/lg/log.do", self.handle_lg_log), # EcoVacs Home - web.post("/api/iot/devmanager.do", self.handle_devmanager_botcommand), - web.post( - "/api/dim/devmanager.do", self.handle_dim_devmanager - ), # EcoVacs Home + web.get("", self.handle_base, name="base"), + web.get("/bot/remove/{did}", self.handle_RemoveBot, name='remove-bot'), + web.get("/client/remove/{resource}", self.handle_RemoveClient, name='remove-client'), + web.get("/restart_{service}", self.handle_RestartService, name='restart-service'), web.post("/lookup.do", self.handle_lookup), - web.get("/api/pim/file/get/{id}", self.handle_pimFile) + ] ) - # Direct register from app: - # /{apiversion}/private/{country}/{language}/{devid}/{apptype}/{appversion}/{devtype}/{aid}/user/directRegister + + # common api paths + api_v1 = {"prefix": "/v1/", "app": web.Application()} # for /v1/ + api_v2 = {"prefix": "/v2/", "app": web.Application()} # for /v2/ + portal_api = {"prefix": "/api/", "app": web.Application()} # for /api/ + + apis = { + "api_v1": api_v1, + "api_v2": api_v2, + "portal_api": portal_api, + + } + + # Load plugins + for plug in bumper.discovered_plugins: + if isinstance(bumper.discovered_plugins[plug].plugin, bumper.plugins.ConfServerApp): + plugin = bumper.discovered_plugins[plug].plugin + if plugin.plugin_type == "sub_api": # app or sub_api + if plugin.sub_api in apis: + if plugin.routes: + logging.debug(f"Adding confserver sub_api ({plugin.name})") + apis[plugin.sub_api]["app"].add_routes(plugin.routes) + + 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) + + for api in apis: + 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): + 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) + site = web.TCPSite( + runner, + host=address, + port=port, + ssl_context=ssl_ctx, + ) + + else: + site = web.TCPSite( + runner, host=address, port=port + ) + + await site.start() async def start_server(self): try: @@ -195,12 +173,12 @@ class ConfServer: async def handle_base(self, request): try: - # TODO - API Options here for viewing clients, tokens, restarting the server, etc. - # text = "Bumper!" + bots = bumper.db_get().table("bots").all() clients = bumper.db_get().table("clients").all() helperbot = bumper.mqtt_helperbot.Client.session.transitions.state mqttserver = bumper.mqtt_server.broker + xmppserver = bumper.xmpp_server mq_sessions = [] for sess in mqttserver._sessions: tmpsess = [] @@ -224,119 +202,136 @@ class ConfServer: ] }, ], - } - - return web.json_response(all) + "xmpp_server": xmppserver + } + resp = aiohttp_jinja2.render_template('home.jinja2', request, context=all) + #return web.json_response(all) + return resp except Exception as e: confserverlog.exception("{}".format(e)) @web.middleware async def log_all_requests(self, request, handler): - - try: - if request.content_length: - if request.content_type == "application/x-www-form-urlencoded": - postbody = await request.post() - elif request.content_type == "application/json": - try: - postbody = json.loads(await request.text()) - except Exception as e: - confserverlog.error("Request body not json: {} - {}".format(e, e.doc)) - postbody = e.doc - - else: - postbody = await request.post() - else: - postbody = None - - response = await handler(request) - if not "application/octet-stream" in response.content_type: - logall = { - "request": { - "route_name": f"{request.match_info.route.name}", - "method": f"{request.method}", - "path": f"{request.path}", - "query_string": f"{request.query_string}", - "raw_path": f"{request.raw_path}", - "raw_headers": f'{",".join(map("{}".format, request.raw_headers))}', - "body": f"{postbody}", - }, - - "response": { - "response_body": f"{json.loads(response.body)}", - "status": f"{response.status}", - } - } - else: - logall = { - "request": { - "route_name": f"{request.match_info.route.name}", - "method": f"{request.method}", - "path": f"{request.path}", - "query_string": f"{request.query_string}", - "raw_path": f"{request.raw_path}", - "raw_headers": f'{",".join(map("{}".format, request.raw_headers))}', - "body": f"{postbody}", - }, - - "response": { - "status": f"{response.status}", - } - } - - confserverlog.debug(json.dumps(logall)) + if request._match_info.route.name not in self.excludelogging: - return response + try: + if request.content_length: + if request.content_type == "application/x-www-form-urlencoded": + postbody = await request.post() - except web.HTTPNotFound as notfound: - confserverlog.debug("Request path {} not found".format(request.raw_path)) - requestlog = { - "request": { - "route_name": f"{request.match_info.route.name}", - "method": f"{request.method}", - "path": f"{request.path}", - "query_string": f"{request.query_string}", - "raw_path": f"{request.raw_path}", - "raw_headers": f'{",".join(map("{}".format, request.raw_headers))}', - "body": f"{postbody}", - } - } - confserverlog.debug(json.dumps(requestlog)) - return notfound + elif request.content_type == "application/json": + try: + postbody = json.loads(await request.text()) + except Exception as e: + confserverlog.error("Request body not json: {} - {}".format(e, e.doc)) + postbody = e.doc + + else: + postbody = await request.post() + else: + postbody = None - except Exception as e: - confserverlog.exception("{}".format(e)) - requestlog = { - "request": { - "route_name": f"{request.match_info.route.name}", - "method": f"{request.method}", - "path": f"{request.path}", - "query_string": f"{request.query_string}", - "raw_path": f"{request.raw_path}", - "raw_headers": f'{",".join(map("{}".format, request.raw_headers))}', - "body": f"{postbody}", - } - } - confserverlog.debug(json.dumps(requestlog)) - return e + response = await handler(request) + if not "application/octet-stream" in response.content_type: + logall = { + "request": { + "route_name": f"{request.match_info.route.name}", + "method": f"{request.method}", + "path": f"{request.path}", + "query_string": f"{request.query_string}", + "raw_path": f"{request.raw_path}", + "raw_headers": f'{",".join(map("{}".format, request.raw_headers))}', + "body": f"{postbody}", + }, + + "response": { + "response_body": f"{json.loads(response.body)}", + "status": f"{response.status}", + } + } + else: + logall = { + "request": { + "route_name": f"{request.match_info.route.name}", + "method": f"{request.method}", + "path": f"{request.path}", + "query_string": f"{request.query_string}", + "raw_path": f"{request.raw_path}", + "raw_headers": f'{",".join(map("{}".format, request.raw_headers))}', + "body": f"{postbody}", + }, + + "response": { + "status": f"{response.status}", + } + } + + confserverlog.debug(json.dumps(logall)) + + return response + + except web.HTTPNotFound as notfound: + confserverlog.debug("Request path {} not found".format(request.raw_path)) + requestlog = { + "request": { + "route_name": f"{request.match_info.route.name}", + "method": f"{request.method}", + "path": f"{request.path}", + "query_string": f"{request.query_string}", + "raw_path": f"{request.raw_path}", + "raw_headers": f'{",".join(map("{}".format, request.raw_headers))}', + "body": f"{postbody}", + } + } + confserverlog.debug(json.dumps(requestlog)) + return notfound + + except Exception as e: + confserverlog.exception("{}".format(e)) + requestlog = { + "request": { + "route_name": f"{request.match_info.route.name}", + "method": f"{request.method}", + "path": f"{request.path}", + "query_string": f"{request.query_string}", + "raw_path": f"{request.raw_path}", + "raw_headers": f'{",".join(map("{}".format, request.raw_headers))}', + "body": f"{postbody}", + } + } + confserverlog.debug(json.dumps(requestlog)) + return e + + else: + return await handler(request) async def restart_Helper(self): await bumper.mqtt_helperbot.Client.disconnect() - await bumper.mqtt_helperbot.start_helper_bot() + asyncio.create_task(bumper.mqtt_helperbot.start_helper_bot()) async def restart_MQTT(self): - mqttserver = bumper.mqtt_server.broker + + if not (bumper.mqtt_server.broker.transitions.state == "stopped" or bumper.mqtt_server.broker.transitions.state == "not_started"): + # close session writers - this was required so bots would reconnect properly after restarting + for sess in list(bumper.mqtt_server.broker._sessions): + sessobj = bumper.mqtt_server.broker._sessions[sess][1] + if sessobj.session.transitions.state == "connected": + await sessobj.writer.close() - await bumper.mqtt_server.broker.shutdown() - while not bumper.mqtt_server.broker.transitions.state == "stopped": - await asyncio.sleep(0.1) + #await bumper.mqtt_server.broker.shutdown() + aloop = asyncio.get_event_loop() + aloop.call_later( + 0.1, lambda: asyncio.create_task(bumper.mqtt_server.broker.shutdown()) + ) # In .1 seconds shutdown broker - await bumper.mqtt_server.broker_coro() - while not bumper.mqtt_server.broker.transitions.state == "started": - await asyncio.sleep(0.1) + + aloop = asyncio.get_event_loop() + aloop.call_later( + 1.5, lambda: asyncio.create_task(bumper.mqtt_server.broker_coro()) + ) # In 1.5 seconds start broker async def restart_XMPP(self): bumper.xmpp_server.disconnect() @@ -349,11 +344,12 @@ class ConfServer: await self.restart_Helper() return web.json_response({"status": "complete"}) elif service == "MQTTServer": - await self.restart_MQTT() + asyncio.create_task(self.restart_MQTT()) aloop = asyncio.get_event_loop() aloop.call_later( - 2, lambda: asyncio.create_task(self.restart_Helper()) - ) # In 2 seconds restart Helperbot + 5, lambda: asyncio.create_task(self.restart_Helper()) + ) # In 5 seconds restart Helperbot + return web.json_response({"status": "complete"}) elif service == "XMPPServer": await self.restart_XMPP() @@ -363,6 +359,33 @@ class ConfServer: except Exception as e: confserverlog.exception("{}".format(e)) + pass + + async def handle_RemoveBot(self, request): + try: + did = request.match_info.get("did", "") + bumper.bot_remove(did) + if bumper.bot_get(did): + return web.json_response({"status": "failed to remove bot"}) + else: + return web.json_response({"status": "successfully removed bot"}) + + except Exception as e: + confserverlog.exception("{}".format(e)) + pass + + async def handle_RemoveClient(self, request): + try: + resource = request.match_info.get("resource", "") + bumper.client_remove(resource) + if bumper.client_get(resource): + return web.json_response({"status": "failed to remove client"}) + else: + return web.json_response({"status": "successfully removed client"}) + + except Exception as e: + confserverlog.exception("{}".format(e)) + pass async def handle_login(self, request): try: @@ -435,655 +458,6 @@ class ConfServer: except Exception as e: confserverlog.exception("{}".format(e)) - def handle_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()), - } - return web.json_response(body) - - except Exception as e: - confserverlog.exception("{}".format(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("{}".format(e)) - - 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("{}".format(e)) - - def generate_authcode(self, user, countrycode, token): - try: - tmpauthcode = "{}_{}".format(countrycode, uuid.uuid4().hex) - bumper.user_add_authcode(user["userid"], token, tmpauthcode) - return tmpauthcode - - except Exception as e: - confserverlog.exception("{}".format(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("No DID for bot: {}".format(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("{}".format(e)) - - async def handle_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("{}".format(e)) - - async def handle_getAuthCode(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("{}".format(e)) - - async def handle_checkVersion(self, request): - try: - body = { - "code": bumper.RETURN_API_SUCCESS, - "data": { - "c": None, - "img": None, - "r": 0, - "t": None, - "u": None, - "ut": 0, - "v": None, - }, - "msg": "操作成功", - "time": self.get_milli_time(datetime.utcnow().timestamp()), - } - - return web.json_response(body) - - except Exception as e: - confserverlog.exception("{}".format(e)) - - async def handle_checkAPPVersion(self, request): # EcoVacs Home - try: - body = { - "code": bumper.RETURN_API_SUCCESS, - "data": { - "c": None, - "downPageUrl": None, - "img": None, - "nextAlertTime": None, - "r": 0, - "t": None, - "u": None, - "ut": 0, - "v": None, - }, - "msg": "操作成功", - "success": True, - "time": self.get_milli_time(datetime.utcnow().timestamp()), - } - - return web.json_response(body) - - except Exception as e: - confserverlog.exception("{}".format(e)) - - async def handle_uploadDeviceInfo(self, request): # EcoVacs Home - try: - body = { - "code": bumper.RETURN_API_SUCCESS, - "data": None, - "msg": "操作成功", - "success": True, - "time": self.get_milli_time(datetime.utcnow().timestamp()), - } - - return web.json_response(body) - - except Exception as e: - confserverlog.exception("{}".format(e)) - - async def handle_getAdByPositionType(self, request): # EcoVacs Home - try: - body = { - "code": bumper.RETURN_API_SUCCESS, - "data": None, - "msg": "操作成功", - "success": True, - "time": self.get_milli_time(datetime.utcnow().timestamp()), - } - - return web.json_response(body) - - except Exception as e: - confserverlog.exception("{}".format(e)) - - async def handle_getBootScreen(self, request): # EcoVacs Home - try: - body = { - "code": bumper.RETURN_API_SUCCESS, - "data": None, - "msg": "操作成功", - "success": True, - "time": self.get_milli_time(datetime.utcnow().timestamp()), - } - - return web.json_response(body) - - except Exception as e: - confserverlog.exception("{}".format(e)) - - async def handle_hasUnreadMessage(self, request): # EcoVacs Home - try: - body = { - "code": bumper.RETURN_API_SUCCESS, - "data": "N", - "msg": "操作成功", - "success": True, - "time": self.get_milli_time(datetime.utcnow().timestamp()), - } - - return web.json_response(body) - - except Exception as e: - confserverlog.exception("{}".format(e)) - - async def handle_neng_hasUnreadMessage(self, request): # EcoVacs Home - try: - body = {"code": 0, "data": {"hasUnRead": True}} - - return web.json_response(body) - - except Exception as e: - confserverlog.exception("{}".format(e)) - - async def handle_getMsgList(self, request): # EcoVacs Home - try: - body = { - "code": bumper.RETURN_API_SUCCESS, - "data": {"hasNextPage": 0, "items": []}, - "msg": "操作成功", - "success": True, - "time": self.get_milli_time(datetime.utcnow().timestamp()), - } - - return web.json_response(body) - - except Exception as e: - confserverlog.exception("{}".format(e)) - - async def handle_getCnWapShopConfig(self, request): # EcoVacs Home - try: - body = { - "code": bumper.RETURN_API_SUCCESS, - "data": { - "myShopShowFlag": "N", - "myShopUrl": "", - "shopIndexShowFlag": "N", - "shopIndexUrl": "", - }, - "msg": "操作成功", - "success": True, - "time": self.get_milli_time(datetime.utcnow().timestamp()), - } - - return web.json_response(body) - - except Exception as e: - confserverlog.exception("{}".format(e)) - - async def handle_getSystemReminder(self, request): # EcoVacs Home - try: - body = { - "code": bumper.RETURN_API_SUCCESS, - "data": { - "iosGradeTime": {"iodGradeFlag": "N"}, - "openNotification": { - "openNotificationContent": None, - "openNotificationFlag": "N", - "openNotificationTitle": None, - }, - }, - "msg": "操作成功", - "success": True, - "time": self.get_milli_time(datetime.utcnow().timestamp()), - } - - return web.json_response(body) - - except Exception as e: - confserverlog.exception("{}".format(e)) - - async def handle_checkAgreement(self, request): - try: - apptype = request.match_info.get("apptype", "") - if "global_" in apptype: - body = { - "code": bumper.RETURN_API_SUCCESS, - "data": [ - { - "force": "N", - "id": "20180804040641_7d746faf18b8cb22a50d145598fe4c90", - "type": "USER", - "url": "https://bumper.ecovacs.com/content/agreement?id=20180804040641_7d746faf18b8cb22a50d145598fe4c90&language=EN", # "https://gl-us-wap.ecovacs.com/content/agreement?id=20180804040641_7d746faf18b8cb22a50d145598fe4c90&language=EN - "version": "1.01", - }, - { - "force": "N", - "id": "20180804040245_4e7c56dfb7ebd3b81b1f2747d0859fac", - "type": "PRIVACY", - "url": "https://bumper.ecovacs.com/content/agreement?id=20180804040245_4e7c56dfb7ebd3b81b1f2747d0859fac&language=EN", # "https://gl-us-wap.ecovacs.com/content/agreement?id=20180804040245_4e7c56dfb7ebd3b81b1f2747d0859fac&language=EN" - "version": "1.01", - }, - ], - "msg": "操作成功", - "success": True, - "time": self.get_milli_time(datetime.utcnow().timestamp()), - } - else: - body = { - "code": bumper.RETURN_API_SUCCESS, - "data": [], - "msg": "操作成功", - "time": self.get_milli_time(datetime.utcnow().timestamp()), - } - - return web.json_response(body) - - except Exception as e: - confserverlog.exception("{}".format(e)) - - async def handle_homePageAlert(self, request): - try: - nextAlert = self.get_milli_time( - (datetime.now() + timedelta(hours=12)).timestamp() - ) - - body = { - "code": bumper.RETURN_API_SUCCESS, - "data": { - "clickSchemeUrl": None, - "clickWebUrl": None, - "hasCampaign": "N", - "imageUrl": None, - "nextAlertTime": nextAlert, - "serverTime": self.get_milli_time(datetime.utcnow().timestamp()), - }, - "msg": "操作成功", - "time": self.get_milli_time(datetime.utcnow().timestamp()), - } - - return web.json_response(body) - - except Exception as e: - confserverlog.exception("{}".format(e)) - - async def handle_getProductIotMap(self, request): - try: - body = { - "code": bumper.RETURN_API_SUCCESS, - "data": EcoVacsHomeProducts, - } - return web.json_response(body) - - except Exception as e: - confserverlog.exception("{}".format(e)) - - async def handle_usersapi(self, request): - if not request.method == "GET": # Skip GET for now - try: - - body = {} - postbody = {} - if request.content_type == "application/x-www-form-urlencoded": - postbody = await request.post() - - else: - postbody = json.loads(await request.text()) - - todo = postbody["todo"] - if todo == "FindBest": - service = postbody["service"] - if service == "EcoMsgNew": - srvip = bumper.bumper_announce_ip - srvport = 5223 - confserverlog.info( - "Announcing EcoMsgNew Server to bot as: {}:{}".format( - 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) - - elif service == "EcoUpdate": - srvip = "47.88.66.164" # EcoVacs Server - srvport = 8005 - confserverlog.info( - "Announcing EcoUpdate Server to bot as: {}:{}".format( - srvip, srvport - ) - ) - body = {"result": "ok", "ip": srvip, "port": srvport} - - elif todo == "loginByItToken": - if "userId" in postbody: - if bumper.check_authcode(postbody["userId"], postbody["token"]): - body = { - "resource": postbody["resource"], - "result": "ok", - "todo": "result", - "token": postbody["token"], - "userId": postbody["userId"], - } - else: # EcoVacs Home LoginByITToken - loginToken = bumper.loginByItToken(postbody["token"]) - if not loginToken == {}: - body = { - "resource": postbody["resource"], - "result": "ok", - "todo": "result", - "token": loginToken["token"], - "userId": loginToken["userid"], - } - else: - body = {"result": "fail", "todo": "result"} - - elif todo == "GetDeviceList": - body = { - "devices": bumper.db_get().table("bots").all(), - "result": "ok", - "todo": "result", - } - - elif todo == "SetDeviceNick": - bumper.bot_set_nick(postbody["did"], postbody["nick"]) - body = {"result": "ok", "todo": "result"} - - elif todo == "AddOneDevice": - bumper.bot_set_nick(postbody["did"], postbody["nick"]) - body = {"result": "ok", "todo": "result"} - - elif todo == "DeleteOneDevice": - bumper.bot_remove(postbody["did"]) - body = {"result": "ok", "todo": "result"} - - return web.json_response(body) - - except Exception as e: - confserverlog.exception("{}".format(e)) - - # Return fail for GET - body = {"result": "fail", "todo": "result"} - return web.json_response(body) - - async def handle_appsvr_api(self, request): - if not request.method == "GET": # Skip GET for now - try: - - body = {} - postbody = {} - if request.content_type == "application/x-www-form-urlencoded": - postbody = await request.post() - - else: - postbody = json.loads(await request.text()) - - todo = postbody["todo"] - - if todo == "GetGlobalDeviceList": # EcoVacs Home - bots = bumper.db_get().table("bots").all() - botlist = [] - for bot in bots: - if bot["class"] != "": - b = bumper.bot_toEcoVacsHome_JSON(bot) - if ( - not b is None - ): # Happens if the bot isn't on the EcoVacs Home list - botlist.append(json.loads(b)) - - body = { - "code": 0, - "devices": botlist, - "ret": "ok", - "todo": "result", - } - - return web.json_response(body) - - except Exception as e: - confserverlog.exception("{}".format(e)) - - # Return fail for GET - body = {"result": "fail", "todo": "result"} - return web.json_response(body) - async def handle_lookup(self, request): try: @@ -1131,181 +505,6 @@ class ConfServer: except Exception as e: confserverlog.exception("{}".format(e)) - async def handle_lg_log(self, request): # EcoVacs Home - try: - json_body = json.loads(await request.text()) - - randomid = "".join(random.sample(string.ascii_letters, 6)) - did = json_body["did"] - - botdetails = bumper.bot_get(did) - if botdetails: - if not "cmdName" in json_body: - if "td" in json_body: - json_body["cmdName"] = json_body["td"] - - if not "toId" in json_body: - json_body["toId"] = did - - if not "toType" in json_body: - json_body["toType"] = botdetails["class"] - - if not "toRes" in json_body: - json_body["toRes"] = botdetails["resource"] - - if not "payloadType" in json_body: - json_body["payloadType"] = "x" - - if not "payload" in json_body: - #json_body["payload"] = "" - if json_body["td"] == "GetCleanLogs": - json_body["td"] = "q" - json_body["payload"] = '' - - if did != "": - bot = bumper.bot_get(did) - if bot["company"] == "eco-ng": - retcmd = await bumper.mqtt_helperbot.send_command( - json_body, randomid - ) - body = retcmd - confserverlog.debug("Send Bot - {}".format(json_body)) - confserverlog.debug("Bot Response - {}".format(body)) - logs = [] - logsroot = ET.fromstring(retcmd["resp"]) - if logsroot.attrib["ret"] == "ok": - cleanlogs = logsroot.getchildren() - for l in cleanlogs: - cleanlog = { - "ts": l.attrib['s'], - "area": l.attrib['a'], - "last": l.attrib['l'], - "cleanType": l.attrib['t'], - #imageUrl allows for providing images of cleanings, something to look into later - #"imageUrl": "https://localhost:8007", - } - logs.append(cleanlog) - body = { - "ret": "ok", - "logs": logs, - } - - else: - body = {"ret": "ok", "logs": []} - - confserverlog.debug("lg logs return: {}".format(json.dumps(body))) - return web.json_response(body) - else: - # No response, send error back - confserverlog.error( - "No bots with DID: {} connected to MQTT".format( - json_body["toId"] - ) - ) - body = {"id": randomid, "errno": bumper.ERR_COMMON, "ret": "fail"} - return web.json_response(body) - - except Exception as e: - confserverlog.exception("{}".format(e)) - - async def handle_devmanager_botcommand(self, request): - try: - json_body = json.loads(await request.text()) - - randomid = "".join(random.sample(string.ascii_letters, 6)) - did = "" - if "toId" in json_body: # Its a command - did = json_body["toId"] - - if did != "": - bot = bumper.bot_get(did) - if bot["company"] == "eco-ng": - retcmd = await bumper.mqtt_helperbot.send_command( - json_body, randomid - ) - body = retcmd - confserverlog.debug("Send Bot - {}".format(json_body)) - confserverlog.debug("Bot Response - {}".format(body)) - return web.json_response(body) - else: - # No response, send error back - confserverlog.error( - "No bots with DID: {} connected to MQTT".format( - json_body["toId"] - ) - ) - body = { - "id": randomid, - "errno": 500, - "ret": "fail", - "debug": "wait for response timed out", - } - return web.json_response(body) - - else: - if "td" in json_body: # Seen when doing initial wifi config - if json_body["td"] == "PollSCResult": - body = {"ret": "ok"} - return web.json_response(body) - - if json_body["td"] == "HasUnreadMsg": # EcoVacs Home - body = {"ret": "ok", "unRead": False} - return web.json_response(body) - - except Exception as e: - confserverlog.exception("{}".format(e)) - - async def handle_dim_devmanager(self, request): # Used in EcoVacs Home App - try: - json_body = json.loads(await request.text()) - - randomid = "".join(random.sample(string.ascii_letters, 6)) - did = "" - if "toId" in json_body: # Its a command - did = json_body["toId"] - - if did != "": - bot = bumper.bot_get(did) - if bot["company"] == "eco-ng" and bot["mqtt_connection"] == True: - retcmd = await bumper.mqtt_helperbot.send_command( - json_body, randomid - ) - body = retcmd - confserverlog.debug("Send Bot - {}".format(json_body)) - confserverlog.debug("Bot Response - {}".format(body)) - return web.json_response(body) - else: - # No response, send error back - confserverlog.error( - "No bots with DID: {} connected to MQTT".format( - json_body["toId"] - ) - ) - body = {"id": randomid, "errno": bumper.ERR_COMMON, "ret": "fail"} - return web.json_response(body) - - else: - if "td" in json_body: # Seen when doing initial wifi config - if json_body["td"] == "PollSCResult": - body = {"ret": "ok"} - return web.json_response(body) - - if json_body["td"] == "HasUnreadMsg": # EcoVacs Home - body = {"ret": "ok", "unRead": False} - return web.json_response(body) - - except Exception as e: - confserverlog.exception("{}".format(e)) - - - async def handle_pimFile(self, request): - try: - fileID = request.match_info.get("id", "") - - return web.FileResponse(os.path.join(bumper.data_dir,"web","robotvac_image.jpg")) - - except Exception as e: - confserverlog.exception("{}".format(e)) async def disconnect(self): try: @@ -1315,3 +514,356 @@ class ConfServer: except Exception as e: confserverlog.exception("{}".format(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("{}".format(e)) + + def generate_authcode(self, user, countrycode, token): + try: + tmpauthcode = "{}_{}".format(countrycode, uuid.uuid4().hex) + bumper.user_add_authcode(user["userid"], token, tmpauthcode) + return tmpauthcode + + except Exception as e: + confserverlog.exception("{}".format(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( + "client with devid {} attempting login".format(user_devid) + ) + 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("{}".format(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("{}".format(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("{}".format(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("No DID for bot: {}".format(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("{}".format(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()), + } + return web.json_response(body) + + except Exception as e: + confserverlog.exception("{}".format(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("{}".format(e)) \ No newline at end of file diff --git a/bumper/db.py b/bumper/db.py index 52f6ba1..71c158a 100644 --- a/bumper/db.py +++ b/bumper/db.py @@ -286,7 +286,8 @@ def bot_add(sn, did, devclass, resource, company): def bot_remove(did): bots = db_get().table("bots") bot = bot_get(did) - bots.remove(doc_ids=[bot.doc_id]) + if bot: + bots.remove(doc_ids=[bot.doc_id]) def bot_get(did): @@ -344,6 +345,11 @@ def client_add(userid, realm, resource): bumperlog.info("Adding new client with resource {}".format(newclient.resource)) client_full_upsert(newclient.asdict()) +def client_remove(resource): + clients = db_get().table("clients") + client = client_get(resource) + if client: + clients.remove(doc_ids=[client.doc_id]) def client_get(resource): clients = db_get().table("clients") diff --git a/bumper/mqttserver.py b/bumper/mqttserver.py index f28a3f5..f53d1f8 100644 --- a/bumper/mqttserver.py +++ b/bumper/mqttserver.py @@ -27,7 +27,7 @@ class MQTTHelperBot: def __init__(self, address): self.address = address - self.client_id = "helper1@bumper/helper1" + self.client_id = "helperbot@bumper/helperbot" self.command_responses = [] async def start_helper_bot(self): @@ -35,7 +35,7 @@ class MQTTHelperBot: try: if self.Client is None: self.Client = MQTTClient( - client_id=self.client_id, config={"check_hostname": False} + client_id=self.client_id, config={"check_hostname": False, "reconnect_retries": 20} ) await self.Client.connect( @@ -44,89 +44,26 @@ class MQTTHelperBot: ) await self.Client.subscribe( [ - ("iot/p2p/+/+/+/+/helper1/bumper/helper1/+/+/+", QOS_0), + ("iot/p2p/+/+/+/+/helperbot/bumper/helperbot/+/+/+", QOS_0), ("iot/p2p/+", QOS_0), ("iot/atr/+", QOS_0), ] ) - asyncio.create_task(self.get_msg()) +# except ConnectionRefusedError as e: +# helperbotlog.Error(e) +# pass - except ConnectionRefusedError as e: - helperbotlog.Error(e) - pass +# except asyncio.CancelledError as e: +# pass - except asyncio.CancelledError as e: - pass - - except hbmqtt.client.ConnectException as e: - helperbotlog.Error(e) - pass +# except hbmqtt.client.ConnectException as e: +# helperbotlog.Error(e) +# pass except Exception as e: helperbotlog.exception("{}".format(e)) - async def get_msg(self): - while True: - message = await self.Client.deliver_message() - - if str(message.topic).split("/")[6] == "helper1": - # Response to command - helperbotlog.debug( - "Received Response - Topic: {} - Message: {}".format( - message.topic, str(message.data.decode("utf-8")) - ) - ) - self.command_responses.append( - { - "time": time.time(), - "topic": message.topic, - "payload": str(message.data.decode("utf-8")), - } - ) - elif str(message.topic).split("/")[3] == "helper1": - # Helperbot sending command - helperbotlog.debug( - "Send Command - Topic: {} - Message: {}".format( - message.topic, str(message.data.decode("utf-8")) - ) - ) - elif str(message.topic).split("/")[1] == "atr": - # Broadcast message received on atr - if str(message.topic).split("/")[2] == "errors": - boterrorlog.error( - "Received Error - Topic: {} - Message: {}".format( - message.topic, str(message.data.decode("utf-8")) - ) - ) - else: - helperbotlog.debug( - "Received Broadcast - Topic: {} - Message: {}".format( - message.topic, str(message.data.decode("utf-8")) - ) - ) - - else: - helperbotlog.debug( - "Received Message - Topic: {} - Message: {}".format( - message.topic, str(message.data.decode("utf-8")) - ) - ) - - # Cleanup "expired messages" > 60 seconds from time - for msg in self.command_responses: - expire_time = ( - datetime.fromtimestamp(msg["time"]) - + timedelta(seconds=self.expire_msg_seconds) - ).timestamp() - if time.time() > expire_time: - helperbotlog.debug( - "Pruning Message Due To Expiration - Message Topic: {}".format( - msg["topic"] - ) - ) - self.command_responses.remove(msg) - async def wait_for_resp(self, requestid): try: @@ -139,8 +76,7 @@ class MQTTHelperBot: if len(self.command_responses) > 0: for msg in self.command_responses: topic = str(msg["topic"]).split("/") - if topic[6] == "helper1" and topic[10] == requestid: - # helperbotlog.debug('VacBot MQTT Response: Topic: %s Payload: %s' % (msg['topic'], msg['payload'])) + if topic[6] == "helperbot" and topic[10] == requestid: if topic[11] == "j": resppayload = json.loads(msg["payload"]) else: @@ -175,7 +111,7 @@ class MQTTHelperBot: async def send_command(self, cmdjson, requestid): if not self.Client._handler.writer is None: try: - ttopic = "iot/p2p/{}/helper1/bumper/helper1/{}/{}/{}/q/{}/{}".format( + ttopic = "iot/p2p/{}/helperbot/bumper/helperbot/{}/{}/{}/q/{}/{}".format( cmdjson["cmdName"], cmdjson["toId"], cmdjson["toType"], @@ -207,32 +143,46 @@ class MQTTHelperBot: class MQTTServer: - default_config = {} + default_config = None broker = None async def broker_coro(self): mqttserverlog.info( "Starting MQTT Server at {}:{}".format(self.address[0], self.address[1]) - ) - self.broker = hbmqtt.broker.Broker(config=self.default_config) + ) try: await self.broker.start() except hbmqtt.broker.BrokerException as e: mqttserverlog.exception(e) - asyncio.create_task(bumper.shutdown()) + #asyncio.create_task(bumper.shutdown()) pass except Exception as e: mqttserverlog.exception("{}".format(e)) - asyncio.create_task(bumper.shutdown()) + #asyncio.create_task(bumper.shutdown()) + pass - def __init__(self, address): + def __init__(self, address, **kwargs): try: self.address = address + # Default config opts + passwd_file = os.path.join( + os.path.join(bumper.data_dir, "passwd") + ) # For file auth, set user:hash in passwd file see (https://hbmqtt.readthedocs.io/en/latest/references/hbmqtt.html#configuration-example) + + allow_anon = False + + for key, value in kwargs.items(): + if key == "password_file": + passwd_file = kwargs["password_file"] + + elif key == "allow_anonymous": + allow_anon = kwargs["allow_anonymous"] # Set to True to allow anonymous authentication + # The below adds a plugin to the hbmqtt.broker.plugins without having to futz with setup.py distribution = pkg_resources.Distribution("hbmqtt.broker.plugins") bumper_plugin = pkg_resources.EntryPoint.parse( @@ -252,17 +202,17 @@ class MQTTServer: "keyfile": bumper.server_key, }, }, - "sys_interval": 10, + "sys_interval": 0, "auth": { - "allow-anonymous": False, # Set to True to allow anonymous authentication - "password-file": os.path.join( - os.path.join(bumper.data_dir, "passwd") - ), # For file auth, set user:hash in passwd file see (https://hbmqtt.readthedocs.io/en/latest/references/hbmqtt.html#configuration-example) + "allow-anonymous": allow_anon, + "password-file": passwd_file, "plugins": ["bumper"], # Bumper plugin provides auth and handling of bots/clients connecting }, "topic-check": {"enabled": False}, } + self.broker = hbmqtt.broker.Broker(config=self.default_config) + except Exception as e: mqttserverlog.exception("{}".format(e)) @@ -284,13 +234,6 @@ class BumperMQTTServer_Plugin: async def authenticate(self, *args, **kwargs): authenticated = False - if not self.auth_config: - # auth config section not found - self.context.logger.warning( - "'auth' section not found in context configuration" - ) - return False - try: session = kwargs.get("session", None) @@ -320,7 +263,7 @@ class BumperMQTTServer_Plugin: realm = tmpclientdetail[0] resource = tmpclientdetail[1] - if userid == "helper1": + if userid == "helperbot": mqttserverlog.info(f"Bumper Authentication Success - Helperbot: {client_id}") authenticated = True else: @@ -383,8 +326,6 @@ class BumperMQTTServer_Plugin: self.context.logger.debug(f"{(len(self._users))} user(s) read from file {password_file}") except FileNotFoundError: self.context.logger.warning(f"Password file {password_file} not found") - else: - self.context.logger.debug("Configuration parameter 'password_file' not found") async def on_broker_client_connected(self, client_id): @@ -401,6 +342,69 @@ class BumperMQTTServer_Plugin: bumper.client_set_mqtt(client["resource"], True) return + async def on_broker_message_received(self, client_id, message): + self.handle_helperbot_msg(client_id, message) + + def handle_helperbot_msg(self, client_id, message): + + if str(message.topic).split("/")[6] == "helperbot": + # Response to command + helperbotlog.debug( + "Received Response - Topic: {} - Message: {}".format( + message.topic, str(message.data.decode("utf-8")) + ) + ) + bumper.mqtt_helperbot.command_responses.append( + { + "time": time.time(), + "topic": message.topic, + "payload": str(message.data.decode("utf-8")), + } + ) + elif str(message.topic).split("/")[3] == "helperbot": + # Helperbot sending command + helperbotlog.debug( + "Send Command - Topic: {} - Message: {}".format( + message.topic, str(message.data.decode("utf-8")) + ) + ) + elif str(message.topic).split("/")[1] == "atr": + # Broadcast message received on atr + if str(message.topic).split("/")[2] == "errors": + boterrorlog.error( + "Received Error - Topic: {} - Message: {}".format( + message.topic, str(message.data.decode("utf-8")) + ) + ) + else: + helperbotlog.debug( + "Received Broadcast - Topic: {} - Message: {}".format( + message.topic, str(message.data.decode("utf-8")) + ) + ) + + else: + helperbotlog.debug( + "Received Message - Topic: {} - Message: {}".format( + message.topic, str(message.data.decode("utf-8")) + ) + ) + + # Cleanup "expired messages" > 60 seconds from time + for msg in bumper.mqtt_helperbot.command_responses: + expire_time = ( + datetime.fromtimestamp(msg["time"]) + + timedelta(seconds=bumper.mqtt_helperbot.expire_msg_seconds) + ).timestamp() + if time.time() > expire_time: + helperbotlog.debug( + "Pruning Message Due To Expiration - Message Topic: {}".format( + msg["topic"] + ) + ) + bumper.mqtt_helperbot.command_responses.remove(msg) + + async def on_broker_client_disconnected(self, client_id): didsplit = str(client_id).split("@") diff --git a/bumper/plugins.py b/bumper/plugins.py new file mode 100644 index 0000000..d1cbc9b --- /dev/null +++ b/bumper/plugins.py @@ -0,0 +1,13 @@ +#!/usr/bin/env python3 +import asyncio +from aiohttp import web + +class ConfServerApp(): + name = None + plugin_type = None + path_prefix = None + app = None + sub_api = None + routes = None + + diff --git a/bumper/plugins/bumper_confserver_portal_appsvr.py b/bumper/plugins/bumper_confserver_portal_appsvr.py new file mode 100644 index 0000000..523ec71 --- /dev/null +++ b/bumper/plugins/bumper_confserver_portal_appsvr.py @@ -0,0 +1,69 @@ +#!/usr/bin/env python3 +import asyncio +from aiohttp import web +from bumper import plugins +import logging +import bumper +from bumper.models import * +from bumper import plugins +from datetime import datetime, timedelta + + +class portal_api_appsvr(plugins.ConfServerApp): + + def __init__(self): + self.name = "portal_api_appsvr" + self.plugin_type = "sub_api" + self.sub_api = "portal_api" + + self.routes = [ + + web.route("*", "/appsvr/app.do", self.handle_appsvr_api, name="portal_api_appsvr_app"), + + ] + + self.get_milli_time = bumper.ConfServer.ConfServer_GeneralFunctions().get_milli_time + + async def handle_appsvr_api(self, request): + if not request.method == "GET": # Skip GET for now + try: + + body = {} + postbody = {} + if request.content_type == "application/x-www-form-urlencoded": + postbody = await request.post() + + else: + postbody = json.loads(await request.text()) + + todo = postbody["todo"] + + if todo == "GetGlobalDeviceList": # EcoVacs Home + bots = bumper.db_get().table("bots").all() + botlist = [] + for bot in bots: + if bot["class"] != "": + b = bumper.bot_toEcoVacsHome_JSON(bot) + if ( + not b is None + ): # Happens if the bot isn't on the EcoVacs Home list + botlist.append(json.loads(b)) + + body = { + "code": 0, + "devices": botlist, + "ret": "ok", + "todo": "result", + } + + return web.json_response(body) + + except Exception as e: + logging.exception("{}".format(e)) + + # Return fail for GET + body = {"result": "fail", "todo": "result"} + return web.json_response(body) + +plugin = portal_api_appsvr() + diff --git a/bumper/plugins/bumper_confserver_portal_dim.py b/bumper/plugins/bumper_confserver_portal_dim.py new file mode 100644 index 0000000..78e4583 --- /dev/null +++ b/bumper/plugins/bumper_confserver_portal_dim.py @@ -0,0 +1,72 @@ +#!/usr/bin/env python3 +import asyncio +from aiohttp import web +from bumper import plugins +import logging +import bumper +from bumper.models import * +from bumper import plugins +from datetime import datetime, timedelta +import string +import random + + +class portal_api_dim(plugins.ConfServerApp): + + def __init__(self): + self.name = "portal_api_dimr" + self.plugin_type = "sub_api" + self.sub_api = "portal_api" + + self.routes = [ + + web.route("*", "/dim/devmanager.do", self.handle_dim_devmanager, name="portal_api_dim_devmanager"), + + ] + + 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()) + + randomid = "".join(random.sample(string.ascii_letters, 6)) + did = "" + if "toId" in json_body: # Its a command + did = json_body["toId"] + + if did != "": + bot = bumper.bot_get(did) + if bot["company"] == "eco-ng" and bot["mqtt_connection"] == True: + retcmd = await bumper.mqtt_helperbot.send_command( + json_body, randomid + ) + body = retcmd + logging.debug("Send Bot - {}".format(json_body)) + logging.debug("Bot Response - {}".format(body)) + return web.json_response(body) + else: + # No response, send error back + logging.error( + "No bots with DID: {} connected to MQTT".format( + json_body["toId"] + ) + ) + body = {"id": randomid, "errno": bumper.ERR_COMMON, "ret": "fail"} + return web.json_response(body) + + else: + if "td" in json_body: # Seen when doing initial wifi config + if json_body["td"] == "PollSCResult": + body = {"ret": "ok"} + return web.json_response(body) + + if json_body["td"] == "HasUnreadMsg": # EcoVacs Home + body = {"ret": "ok", "unRead": False} + return web.json_response(body) + + except Exception as e: + logging.exception("{}".format(e)) + +plugin = portal_api_dim() + diff --git a/bumper/plugins/bumper_confserver_portal_iot.py b/bumper/plugins/bumper_confserver_portal_iot.py new file mode 100644 index 0000000..6023db6 --- /dev/null +++ b/bumper/plugins/bumper_confserver_portal_iot.py @@ -0,0 +1,76 @@ +#!/usr/bin/env python3 +import asyncio +from aiohttp import web +from bumper import plugins +import logging +import bumper +from bumper.models import * +from bumper import plugins +from datetime import datetime, timedelta +import string +import random + +class portal_api_iot(plugins.ConfServerApp): + + def __init__(self): + self.name = "portal_api_iot" + self.plugin_type = "sub_api" + self.sub_api = "portal_api" + + self.routes = [ + + web.route("*", "/iot/devmanager.do", self.handle_devmanager_botcommand, name="portal_api_iot_devmanager"), + + ] + + 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()) + + randomid = "".join(random.sample(string.ascii_letters, 6)) + did = "" + if "toId" in json_body: # Its a command + did = json_body["toId"] + + if did != "": + bot = bumper.bot_get(did) + if bot["company"] == "eco-ng": + retcmd = await bumper.mqtt_helperbot.send_command( + json_body, randomid + ) + body = retcmd + logging.debug("Send Bot - {}".format(json_body)) + logging.debug("Bot Response - {}".format(body)) + return web.json_response(body) + else: + # No response, send error back + logging.error( + "No bots with DID: {} connected to MQTT".format( + json_body["toId"] + ) + ) + body = { + "id": randomid, + "errno": 500, + "ret": "fail", + "debug": "wait for response timed out", + } + return web.json_response(body) + + else: + if "td" in json_body: # Seen when doing initial wifi config + if json_body["td"] == "PollSCResult": + body = {"ret": "ok"} + return web.json_response(body) + + if json_body["td"] == "HasUnreadMsg": # EcoVacs Home + body = {"ret": "ok", "unRead": False} + return web.json_response(body) + + except Exception as e: + logging.exception("{}".format(e)) + +plugin = portal_api_iot() + diff --git a/bumper/plugins/bumper_confserver_portal_lg.py b/bumper/plugins/bumper_confserver_portal_lg.py new file mode 100644 index 0000000..c43d59b --- /dev/null +++ b/bumper/plugins/bumper_confserver_portal_lg.py @@ -0,0 +1,108 @@ +#!/usr/bin/env python3 +import asyncio +from aiohttp import web +from bumper import plugins +import logging +import bumper +from bumper.models import * +from bumper import plugins +from datetime import datetime, timedelta +import os +import string +import random +import xml.etree.ElementTree as ET + +class portal_api_lg(plugins.ConfServerApp): + + def __init__(self): + self.name = "portal_api_lg" + self.plugin_type = "sub_api" + self.sub_api = "portal_api" + + self.routes = [ + + 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 + try: + json_body = json.loads(await request.text()) + + randomid = "".join(random.sample(string.ascii_letters, 6)) + did = json_body["did"] + + botdetails = bumper.bot_get(did) + if botdetails: + if not "cmdName" in json_body: + if "td" in json_body: + json_body["cmdName"] = json_body["td"] + + if not "toId" in json_body: + json_body["toId"] = did + + if not "toType" in json_body: + json_body["toType"] = botdetails["class"] + + if not "toRes" in json_body: + json_body["toRes"] = botdetails["resource"] + + if not "payloadType" in json_body: + json_body["payloadType"] = "x" + + if not "payload" in json_body: + #json_body["payload"] = "" + if json_body["td"] == "GetCleanLogs": + json_body["td"] = "q" + json_body["payload"] = '' + + if did != "": + bot = bumper.bot_get(did) + if bot["company"] == "eco-ng": + retcmd = await bumper.mqtt_helperbot.send_command( + json_body, randomid + ) + body = retcmd + logging.debug("Send Bot - {}".format(json_body)) + logging.debug("Bot Response - {}".format(body)) + logs = [] + logsroot = ET.fromstring(retcmd["resp"]) + if logsroot.attrib["ret"] == "ok": + cleanlogs = logsroot.getchildren() + for l in cleanlogs: + cleanlog = { + "ts": l.attrib['s'], + "area": l.attrib['a'], + "last": l.attrib['l'], + "cleanType": l.attrib['t'], + #imageUrl allows for providing images of cleanings, something to look into later + #"imageUrl": "https://localhost:8007", + } + logs.append(cleanlog) + body = { + "ret": "ok", + "logs": logs, + } + + else: + body = {"ret": "ok", "logs": []} + + logging.debug("lg logs return: {}".format(json.dumps(body))) + return web.json_response(body) + else: + # No response, send error back + logging.error( + "No bots with DID: {} connected to MQTT".format( + json_body["toId"] + ) + ) + body = {"id": randomid, "errno": bumper.ERR_COMMON, "ret": "fail"} + return web.json_response(body) + + except Exception as e: + logging.exception("{}".format(e)) + +plugin = portal_api_lg() + diff --git a/bumper/plugins/bumper_confserver_portal_neng.py b/bumper/plugins/bumper_confserver_portal_neng.py new file mode 100644 index 0000000..944c3aa --- /dev/null +++ b/bumper/plugins/bumper_confserver_portal_neng.py @@ -0,0 +1,37 @@ +#!/usr/bin/env python3 +import asyncio +from aiohttp import web +from bumper import plugins +import logging +import bumper +from bumper.models import * +from bumper import plugins +from datetime import datetime, timedelta + + +class portal_api_neng(plugins.ConfServerApp): + + def __init__(self): + self.name = "portal_api_neng" + self.plugin_type = "sub_api" + self.sub_api = "portal_api" + + self.routes = [ + + web.route("*", "/neng/message/hasUnreadMsg", self.handle_neng_hasUnreadMessage, name="portal_api_neng_hasUnreadMessage"), + + ] + + 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}} + + return web.json_response(body) + + except Exception as e: + logging.exception("{}".format(e)) + +plugin = portal_api_neng() + diff --git a/bumper/plugins/bumper_confserver_portal_pim.py b/bumper/plugins/bumper_confserver_portal_pim.py new file mode 100644 index 0000000..2473823 --- /dev/null +++ b/bumper/plugins/bumper_confserver_portal_pim.py @@ -0,0 +1,49 @@ +#!/usr/bin/env python3 +import asyncio +from aiohttp import web +from bumper import plugins +import logging +import bumper +from bumper.models import * +from bumper import plugins +from datetime import datetime, timedelta +import os + +class portal_api_pim(plugins.ConfServerApp): + + def __init__(self): + self.name = "portal_api_pim" + self.plugin_type = "sub_api" + self.sub_api = "portal_api" + + self.routes = [ + + web.route("*", "/pim/product/getProductIotMap", self.handle_getProductIotMap, name="portal_api_pim_getProductIotMap"), + web.route("*", "/pim/file/get/{id}", self.handle_pimFile, name="portal_api_pim_file"), + + ] + + self.get_milli_time = bumper.ConfServer.ConfServer_GeneralFunctions().get_milli_time + + async def handle_getProductIotMap(self, request): + try: + body = { + "code": bumper.RETURN_API_SUCCESS, + "data": EcoVacsHomeProducts, + } + return web.json_response(body) + + except Exception as e: + logging.exception("{}".format(e)) + + async def handle_pimFile(self, request): + try: + fileID = request.match_info.get("id", "") + + return web.FileResponse(os.path.join(bumper.data_dir,"web","robotvac_image.jpg")) + + except Exception as e: + logging.exception("{}".format(e)) + +plugin = portal_api_pim() + diff --git a/bumper/plugins/bumper_confserver_portal_users.py b/bumper/plugins/bumper_confserver_portal_users.py new file mode 100644 index 0000000..fd4d5f8 --- /dev/null +++ b/bumper/plugins/bumper_confserver_portal_users.py @@ -0,0 +1,120 @@ +#!/usr/bin/env python3 +import asyncio +from aiohttp import web +from bumper import plugins +import logging +import bumper +from bumper.models import * +from bumper import plugins +from datetime import datetime, timedelta + + +class portal_api_users(plugins.ConfServerApp): + + def __init__(self): + self.name = "portal_api_users" + self.plugin_type = "sub_api" + self.sub_api = "portal_api" + + self.routes = [ + + web.route("*", "/users/user.do", self.handle_usersapi, name="portal_api_users_user"), + + ] + + 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: + + body = {} + postbody = {} + if request.content_type == "application/x-www-form-urlencoded": + postbody = await request.post() + + else: + postbody = json.loads(await request.text()) + + todo = postbody["todo"] + if todo == "FindBest": + service = postbody["service"] + if service == "EcoMsgNew": + srvip = bumper.bumper_announce_ip + srvport = 5223 + logging.info( + "Announcing EcoMsgNew Server to bot as: {}:{}".format( + 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) + + elif service == "EcoUpdate": + srvip = "47.88.66.164" # EcoVacs Server + srvport = 8005 + logging.info( + "Announcing EcoUpdate Server to bot as: {}:{}".format( + srvip, srvport + ) + ) + body = {"result": "ok", "ip": srvip, "port": srvport} + + elif todo == "loginByItToken": + if "userId" in postbody: + if bumper.check_authcode(postbody["userId"], postbody["token"]): + body = { + "resource": postbody["resource"], + "result": "ok", + "todo": "result", + "token": postbody["token"], + "userId": postbody["userId"], + } + else: # EcoVacs Home LoginByITToken + loginToken = bumper.loginByItToken(postbody["token"]) + if not loginToken == {}: + body = { + "resource": postbody["resource"], + "result": "ok", + "todo": "result", + "token": loginToken["token"], + "userId": loginToken["userid"], + } + else: + body = {"result": "fail", "todo": "result"} + + elif todo == "GetDeviceList": + body = { + "devices": bumper.db_get().table("bots").all(), + "result": "ok", + "todo": "result", + } + + elif todo == "SetDeviceNick": + bumper.bot_set_nick(postbody["did"], postbody["nick"]) + body = {"result": "ok", "todo": "result"} + + elif todo == "AddOneDevice": + bumper.bot_set_nick(postbody["did"], postbody["nick"]) + body = {"result": "ok", "todo": "result"} + + elif todo == "DeleteOneDevice": + bumper.bot_remove(postbody["did"]) + body = {"result": "ok", "todo": "result"} + + return web.json_response(body) + + except Exception as e: + logging.exception("{}".format(e)) + + # 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_v1_global_auth.py b/bumper/plugins/bumper_confserver_v1_global_auth.py new file mode 100644 index 0000000..bbceb70 --- /dev/null +++ b/bumper/plugins/bumper_confserver_v1_global_auth.py @@ -0,0 +1,28 @@ +#!/usr/bin/env python3 +import asyncio +from aiohttp import web +from bumper import plugins +import logging +import bumper +from bumper.models import * +from bumper import plugins +from datetime import datetime, timedelta + + +class v1_global_auth(plugins.ConfServerApp): + + def __init__(self): + self.name = "v1_global_auth" + 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, 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 new file mode 100644 index 0000000..2e7df5b --- /dev/null +++ b/bumper/plugins/bumper_confserver_v1_private_ad.py @@ -0,0 +1,58 @@ +#!/usr/bin/env python3 +import asyncio +from aiohttp import web +from bumper import plugins +import logging +import bumper +from bumper.models import * +from bumper import plugins +from datetime import datetime, timedelta + + +class v1_private_ad(plugins.ConfServerApp): + + def __init__(self): + self.name = "v1_private_ad" + self.plugin_type = "sub_api" + self.sub_api = "api_v1" + + self.routes = [ + web.route("*", "/private/{country}/{language}/{devid}/{apptype}/{appversion}/{devtype}/{aid}/ad/getAdByPositionType", self.handle_getAdByPositionType, name="v1_ad_getAdByPositionType"), + web.route("*", "/private/{country}/{language}/{devid}/{apptype}/{appversion}/{devtype}/{aid}/ad/getBootScreen", self.handle_getBootScreen, name="v1_ad_getBootScreen"), + + ] + + self.get_milli_time = bumper.ConfServer.ConfServer_GeneralFunctions().get_milli_time + + async def handle_getAdByPositionType(self, request): # EcoVacs Home + try: + body = { + "code": bumper.RETURN_API_SUCCESS, + "data": None, + "msg": "操作成功", + "success": True, + "time": self.get_milli_time(datetime.utcnow().timestamp()), + } + + return web.json_response(body) + + except Exception as e: + logging.exception("{}".format(e)) + + async def handle_getBootScreen(self, request): # EcoVacs Home + try: + body = { + "code": bumper.RETURN_API_SUCCESS, + "data": None, + "msg": "操作成功", + "success": True, + "time": self.get_milli_time(datetime.utcnow().timestamp()), + } + + return web.json_response(body) + + except Exception as e: + logging.exception("{}".format(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 new file mode 100644 index 0000000..468ca8c --- /dev/null +++ b/bumper/plugins/bumper_confserver_v1_private_campaign.py @@ -0,0 +1,54 @@ +#!/usr/bin/env python3 +import asyncio +from aiohttp import web +from bumper import plugins +import logging +import bumper +from bumper.models import * +from bumper import plugins +from datetime import datetime, timedelta + + +class v1_private_campaign(plugins.ConfServerApp): + + def __init__(self): + self.name = "v1_private_campaign" + self.plugin_type = "sub_api" + self.sub_api = "api_v1" + + self.routes = [ + + web.route("*", "/private/{country}/{language}/{devid}/{apptype}/{appversion}/{devtype}/{aid}/campaign/homePageAlert", self.handle_homePageAlert, name="v1_campaign_homePageAlert"), + + ] + + self.get_milli_time = bumper.ConfServer.ConfServer_GeneralFunctions().get_milli_time + + async def handle_homePageAlert(self, request): + try: + nextAlert = self.get_milli_time( + (datetime.now() + timedelta(hours=12)).timestamp() + ) + + body = { + "code": bumper.RETURN_API_SUCCESS, + "data": { + "clickSchemeUrl": None, + "clickWebUrl": None, + "hasCampaign": "N", + "imageUrl": None, + "nextAlertTime": nextAlert, + "serverTime": self.get_milli_time(datetime.utcnow().timestamp()), + }, + "msg": "操作成功", + "time": self.get_milli_time(datetime.utcnow().timestamp()), + } + + return web.json_response(body) + + except Exception as e: + logging.exception("{}".format(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 new file mode 100644 index 0000000..71a0034 --- /dev/null +++ b/bumper/plugins/bumper_confserver_v1_private_common.py @@ -0,0 +1,115 @@ +#!/usr/bin/env python3 +import asyncio +from aiohttp import web +from bumper import plugins +import logging +import bumper +from bumper.models import * +from bumper import plugins +from datetime import datetime, timedelta + + +class v1_private_common(plugins.ConfServerApp): + + def __init__(self): + self.name = "v1_private_common" + self.plugin_type = "sub_api" + self.sub_api = "api_v1" + + self.routes = [ + web.route("*", "/private/{country}/{language}/{devid}/{apptype}/{appversion}/{devtype}/{aid}/common/checkAPPVersion", self.handle_checkAPPVersion, name="v1_common_checkAppVersion"), + web.route("*", "/private/{country}/{language}/{devid}/{apptype}/{appversion}/{devtype}/{aid}/common/checkVersion", self.handle_checkVersion, name="v1_common_checkVersion"), + web.route("*", "/private/{country}/{language}/{devid}/{apptype}/{appversion}/{devtype}/{aid}/common/uploadDeviceInfo", self.handle_uploadDeviceInfo, name="v1_common_uploadDeviceInfo"), + web.route("*", "/private/{country}/{language}/{devid}/{apptype}/{appversion}/{devtype}/{aid}/common/getSystemReminder", self.handle_getSystemReminder, name="v1_common_getSystemReminder"), + + ] + + self.get_milli_time = bumper.ConfServer.ConfServer_GeneralFunctions().get_milli_time + + async def handle_checkVersion(self, request): + try: + body = { + "code": bumper.RETURN_API_SUCCESS, + "data": { + "c": None, + "img": None, + "r": 0, + "t": None, + "u": None, + "ut": 0, + "v": None, + }, + "msg": "操作成功", + "time": self.get_milli_time(datetime.utcnow().timestamp()), + } + + return web.json_response(body) + + except Exception as e: + logging.exception("{}".format(e)) + + async def handle_checkAPPVersion(self, request): # EcoVacs Home + try: + body = { + "code": bumper.RETURN_API_SUCCESS, + "data": { + "c": None, + "downPageUrl": None, + "img": None, + "nextAlertTime": None, + "r": 0, + "t": None, + "u": None, + "ut": 0, + "v": None, + }, + "msg": "操作成功", + "success": True, + "time": self.get_milli_time(datetime.utcnow().timestamp()), + } + + return web.json_response(body) + + except Exception as e: + logging.exception("{}".format(e)) + + async def handle_uploadDeviceInfo(self, request): # EcoVacs Home + try: + body = { + "code": bumper.RETURN_API_SUCCESS, + "data": None, + "msg": "操作成功", + "success": True, + "time": self.get_milli_time(datetime.utcnow().timestamp()), + } + + return web.json_response(body) + + except Exception as e: + logging.exception("{}".format(e)) + + async def handle_getSystemReminder(self, request): # EcoVacs Home + try: + body = { + "code": bumper.RETURN_API_SUCCESS, + "data": { + "iosGradeTime": {"iodGradeFlag": "N"}, + "openNotification": { + "openNotificationContent": None, + "openNotificationFlag": "N", + "openNotificationTitle": None, + }, + }, + "msg": "操作成功", + "success": True, + "time": self.get_milli_time(datetime.utcnow().timestamp()), + } + + return web.json_response(body) + + except Exception as e: + logging.exception("{}".format(e)) + + +plugin = v1_private_common() + diff --git a/bumper/plugins/bumper_confserver_v1_private_message.py b/bumper/plugins/bumper_confserver_v1_private_message.py new file mode 100644 index 0000000..7e1d6d1 --- /dev/null +++ b/bumper/plugins/bumper_confserver_v1_private_message.py @@ -0,0 +1,59 @@ +#!/usr/bin/env python3 +import asyncio +from aiohttp import web +from bumper import plugins +import logging +import bumper +from bumper.models import * +from bumper import plugins +from datetime import datetime, timedelta + + +class v1_private_message(plugins.ConfServerApp): + + def __init__(self): + self.name = "v1_private_message" + self.plugin_type = "sub_api" + self.sub_api = "api_v1" + + self.routes = [ + + web.route("*", "/private/{country}/{language}/{devid}/{apptype}/{appversion}/{devtype}/{aid}/message/hasUnreadMsg", self.handle_hasUnreadMessage, name="v1_message_hasUnreadMsg"), + web.route("*", "/private/{country}/{language}/{devid}/{apptype}/{appversion}/{devtype}/{aid}/message/getMsgList", self.handle_getMsgList, name="v1_message_getMsgList"), + + ] + + self.get_milli_time = bumper.ConfServer.ConfServer_GeneralFunctions().get_milli_time + + async def handle_hasUnreadMessage(self, request): # EcoVacs Home + try: + body = { + "code": bumper.RETURN_API_SUCCESS, + "data": "N", + "msg": "操作成功", + "success": True, + "time": self.get_milli_time(datetime.utcnow().timestamp()), + } + + return web.json_response(body) + + except Exception as e: + logging.exception("{}".format(e)) + + async def handle_getMsgList(self, request): # EcoVacs Home + try: + body = { + "code": bumper.RETURN_API_SUCCESS, + "data": {"hasNextPage": 0, "items": []}, + "msg": "操作成功", + "success": True, + "time": self.get_milli_time(datetime.utcnow().timestamp()), + } + + return web.json_response(body) + + except Exception as e: + logging.exception("{}".format(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 new file mode 100644 index 0000000..343f13a --- /dev/null +++ b/bumper/plugins/bumper_confserver_v1_private_shop.py @@ -0,0 +1,48 @@ +#!/usr/bin/env python3 +import asyncio +from aiohttp import web +from bumper import plugins +import logging +import bumper +from bumper.models import * +from bumper import plugins +from datetime import datetime, timedelta + + +class v1_private_shop(plugins.ConfServerApp): + + def __init__(self): + self.name = "v1_private_shop" + self.plugin_type = "sub_api" + self.sub_api = "api_v1" + + self.routes = [ + + web.route("*", "/private/{country}/{language}/{devid}/{apptype}/{appversion}/{devtype}/{aid}/shop/getCnWapShopConfig", self.handle_getCnWapShopConfig, name="v1_shop_getCnWapShopConfig"), + + ] + + self.get_milli_time = bumper.ConfServer.ConfServer_GeneralFunctions().get_milli_time + + async def handle_getCnWapShopConfig(self, request): # EcoVacs Home + try: + body = { + "code": bumper.RETURN_API_SUCCESS, + "data": { + "myShopShowFlag": "N", + "myShopUrl": "", + "shopIndexShowFlag": "N", + "shopIndexUrl": "", + }, + "msg": "操作成功", + "success": True, + "time": self.get_milli_time(datetime.utcnow().timestamp()), + } + + return web.json_response(body) + + except Exception as e: + logging.exception("{}".format(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 new file mode 100644 index 0000000..397fcd1 --- /dev/null +++ b/bumper/plugins/bumper_confserver_v1_private_user.py @@ -0,0 +1,76 @@ +#!/usr/bin/env python3 +import asyncio +from aiohttp import web +from bumper import plugins +import logging +import bumper +from bumper.models import * +from bumper import plugins +from datetime import datetime, timedelta + + +class v1_private_user(plugins.ConfServerApp): + + def __init__(self): + + self.name = "v1_private_user" + 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, name="v1_user_login"), + web.route("*", "/private/{country}/{language}/{devid}/{apptype}/{appversion}/{devtype}/{aid}/user/checkLogin", authhandler.login, name="v1_user_checkLogin"), + web.route("*", "/private/{country}/{language}/{devid}/{apptype}/{appversion}/{devtype}/{aid}/user/getAuthCode", authhandler.get_AuthCode, name="v1_user_getAuthCode"), + web.route("*", "/private/{country}/{language}/{devid}/{apptype}/{appversion}/{devtype}/{aid}/user/logout", authhandler.logout, name="v1_user_logout"), + web.route("*", "/private/{country}/{language}/{devid}/{apptype}/{appversion}/{devtype}/{aid}/user/checkAgreement", self.handle_checkAgreement,name="v1_user_checkAgreement"), + web.route("*", "/private/{country}/{language}/{devid}/{apptype}/{appversion}/{devtype}/{aid}/user/checkAgreementBatch", self.handle_checkAgreement,name="v1_user_checkAgreementBatch"), + web.route("*", "/private/{country}/{language}/{devid}/{apptype}/{appversion}/{devtype}/{aid}/user/getUserAccountInfo", authhandler.getUserAccountInfo,name="v1_user_getUserAccountInfo"), + # Direct register from app: + # /{apiversion}/private/{country}/{language}/{devid}/{apptype}/{appversion}/{devtype}/{aid}/user/directRegister + + ] + + self.get_milli_time = bumper.ConfServer.ConfServer_GeneralFunctions().get_milli_time + + async def handle_checkAgreement(self, request): + try: + apptype = request.match_info.get("apptype", "") + if "global_" in apptype: + body = { + "code": bumper.RETURN_API_SUCCESS, + "data": [ + { + "force": "N", + "id": "20180804040641_7d746faf18b8cb22a50d145598fe4c90", + "type": "USER", + "url": "https://bumper.ecovacs.com/content/agreement?id=20180804040641_7d746faf18b8cb22a50d145598fe4c90&language=EN", # "https://gl-us-wap.ecovacs.com/content/agreement?id=20180804040641_7d746faf18b8cb22a50d145598fe4c90&language=EN + "version": "1.01", + }, + { + "force": "N", + "id": "20180804040245_4e7c56dfb7ebd3b81b1f2747d0859fac", + "type": "PRIVACY", + "url": "https://bumper.ecovacs.com/content/agreement?id=20180804040245_4e7c56dfb7ebd3b81b1f2747d0859fac&language=EN", # "https://gl-us-wap.ecovacs.com/content/agreement?id=20180804040245_4e7c56dfb7ebd3b81b1f2747d0859fac&language=EN" + "version": "1.01", + }, + ], + "msg": "操作成功", + "success": True, + "time": self.get_milli_time(datetime.utcnow().timestamp()), + } + else: + body = { + "code": bumper.RETURN_API_SUCCESS, + "data": [], + "msg": "操作成功", + "time": self.get_milli_time(datetime.utcnow().timestamp()), + } + + return web.json_response(body) + + except Exception as e: + logging.exception("{}".format(e)) + +plugin = v1_private_user() + diff --git a/bumper/plugins/bumper_confserver_v2_private_user.py b/bumper/plugins/bumper_confserver_v2_private_user.py new file mode 100644 index 0000000..5a3c7a3 --- /dev/null +++ b/bumper/plugins/bumper_confserver_v2_private_user.py @@ -0,0 +1,29 @@ +#!/usr/bin/env python3 +import asyncio +from aiohttp import web +from bumper import plugins +import logging +import bumper +from bumper.models import * +from bumper import plugins +from datetime import datetime, timedelta + + +class v2_private_user(plugins.ConfServerApp): + + def __init__(self): + + self.name = "v2_private_user" + 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, name="v2_user_checkLogin"), + ] + + self.get_milli_time = bumper.ConfServer.ConfServer_GeneralFunctions().get_milli_time + + +plugin = v2_private_user() + diff --git a/data/web/templates/home.jinja2 b/data/web/templates/home.jinja2 new file mode 100644 index 0000000..5e74303 --- /dev/null +++ b/data/web/templates/home.jinja2 @@ -0,0 +1,196 @@ + + + + + Bumper + + + + + + +
+

Bumper

+ + +
+
+

Server Status

+
+
+ +
+
+

MQTT Server

+
Action:
+
+
+ Status: {% if mqtt_server[0].state == "started" %} {{ mqtt_server[0].state }} {% else %} {{ mqtt_server[0].state }} {% endif %} + Sessions: {{ mqtt_server[1].sessions[0].count }} + + + + + + {% for sess in mqtt_server[1].sessions[1].clients %} + + + + + + {% endfor %} +
usernameclientidstate
{{ sess[0].username }}{{ sess[0].client_id }} {{ sess[0].state }}
+ +
+
+ +
+ +
+
+

XMPP Server

+
Action:
+
+
+ Status: {% if xmpp_server.server._serving == True %} running {% else %} not running {% endif %} + Clients: {{ xmpp_server.clients | length }} + + + + + + {% for client in xmpp_server.clients %} + + + + + + {% endfor %} +
uidjidstate
{{ client.uid }}{{ client.bumper_jid }}{% if client.state == 4 %} connected {% else %} not connected {% endif %}
+ +
+
+ +
+ +
+
+

Helperbot

+
Action:
+ +
+ +
+ Status: {% if helperbot[0].state == "connected" %} {{ helperbot[0].state }} {% else %} {{ helperbot[0].state }} {% endif %} + +
+
+ +
+
+ +
+ + +
+
+

Bots

+
+
+ + + + + {% for bot in bots %} + + + + + + + + + + + + + {% endfor %} +
SNNicknameClassDIDResourceCompanyMQTT ConnectedXMPP ConnectedAction
{{ bot.name }}{{ bot.nick }}{{ bot.class }}{{ bot.did }}{{ bot.resource }} {{ bot.company}} {{ bot.mqtt_connection }} {{ bot.xmpp_connection }}
+
+
+ +
+ + +
+
+

Clients

+
+
+ + + + + {% for client in clients %} + + + + + + + + + {% endfor %} +
User IDRealmResourceMQTT ConnectedXMPP ConnectedAction
{{ client.userid }}{{ client.realm }}{{ client.resource }} {{ client.mqtt_connection }} {{ client.xmpp_connection }}
+
+
+ +
+ + \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 29019d1..46c5ac6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,5 @@ aiohttp==3.6.2 +aiohttp-jinja2==1.2.0 async-timeout==3.0.1 atomicwrites==1.3.0 attrs==19.3.0 @@ -6,9 +7,11 @@ chardet==3.0.4 docopt==0.6.2 hbmqtt==0.9.5 idna==2.8 +Jinja2==2.10.3 +MarkupSafe==1.1.1 multidict==4.5.2 passlib==1.7.2 -PyYAML==5.2 +PyYAML==5.3b1 six==1.13.0 tinydb==3.15.2 transitions==0.7.1 diff --git a/tests/test_confserver.py b/tests/test_confserver.py index 6e35542..2149673 100644 --- a/tests/test_confserver.py +++ b/tests/test_confserver.py @@ -9,6 +9,9 @@ import pytest_aiohttp import pytest_asyncio import datetime, time from aiohttp import web +import logging +from testfixtures import LogCapture +from unittest.mock import MagicMock def create_confserver(): @@ -37,6 +40,31 @@ async def test_confserver_ssl(): conf_server.confserver_app() asyncio.create_task(conf_server.start_server()) +async def test_confserver_exceptions(): + with LogCapture() as l: + + conf_server = bumper.ConfServer(("127.0.0.1", 8007), usessl=True) + conf_server.confserver_app() + conf_server.site = web.TCPSite + + #bind permission + conf_server.site.start = mock.Mock(side_effect=OSError(1, "error while attempting to bind on address ('127.0.0.1', 8007): permission denied")) + await conf_server.start_server() + + #asyncio Cancel + conf_server.site = web.TCPSite + conf_server.site.start = mock.Mock(side_effect=asyncio.CancelledError) + await conf_server.start_server() + + #general exception + conf_server.site = web.TCPSite + conf_server.site.start = mock.Mock(side_effect=Exception(1, "general")) + await conf_server.start_server() + + l.check_present( + ("confserver", "ERROR", "error while attempting to bind on address ('127.0.0.1', 8007): permission denied") + ) + async def test_confserver_no_ssl(): conf_server = bumper.ConfServer(("127.0.0.1", 111111), usessl=False) @@ -62,9 +90,15 @@ async def test_base(aiohttp_client): # Start MQTT mqtt_address = ("127.0.0.1", 8883) - mqtt_server = bumper.MQTTServer(mqtt_address) + mqtt_server = bumper.MQTTServer(mqtt_address, password_file="tests/passwd") bumper.mqtt_server = mqtt_server await mqtt_server.broker_coro() + + # Start XMPP + xmpp_address = ("127.0.0.1", 5223) + xmpp_server = bumper.XMPPServer(xmpp_address) + bumper.xmpp_server = xmpp_server + await xmpp_server.start_async_server() # Start Helperbot mqtt_helperbot = bumper.MQTTHelperBot(mqtt_address) @@ -79,13 +113,16 @@ async def test_base(aiohttp_client): await mqtt_server.broker.shutdown() + bumper.xmpp_server.disconnect() + + async def test_restartService(aiohttp_client): remove_existing_db() bumper.db = "tests/tmp.db" # Set db location for testing # Start MQTT mqtt_address = ("127.0.0.1", 8883) - mqtt_server = bumper.MQTTServer(mqtt_address) + mqtt_server = bumper.MQTTServer(mqtt_address, password_file="tests/passwd") bumper.mqtt_server = mqtt_server await mqtt_server.broker_coro() @@ -112,10 +149,20 @@ async def test_restartService(aiohttp_client): assert resp.status == 200 mqtt_helperbot.Client.disconnect() - await mqtt_server.broker.shutdown() + await mqtt_server.broker.shutdown() xmpp_server.disconnect() +async def test_RemoveBot(aiohttp_client): + client = await aiohttp_client(create_app) + resp = await client.get("/bot/remove/test_did") + assert resp.status == 200 + +async def test_RemoveClient(aiohttp_client): + client = await aiohttp_client(create_app) + resp = await client.get("/client/remove/test_resource") + assert resp.status == 200 + async def test_login(aiohttp_client): remove_existing_db() diff --git a/tests/test_db.py b/tests/test_db.py index 52c49a4..a23b368 100644 --- a/tests/test_db.py +++ b/tests/test_db.py @@ -141,5 +141,8 @@ def test_client_db(): bumper.client_get("resource_123")["xmpp_connection"] == False ) # Test that xmpp was set False for client assert ( - len(bumper.get_disconnected_xmpp_clients()) == 1 + len(bumper.get_disconnected_xmpp_clients()) > 0 ) # Test len of connected xmpp clients is 1 + + bumper.client_remove("resource_123") + assert bumper.client_get("resource_123") == None diff --git a/tests/test_mqttserver.py b/tests/test_mqttserver.py index 6cb9730..70205bc 100644 --- a/tests/test_mqttserver.py +++ b/tests/test_mqttserver.py @@ -15,7 +15,7 @@ import time async def test_helperbot_message(): mqtt_address = ("127.0.0.1", 8883) - mqtt_server = bumper.MQTTServer(mqtt_address) + mqtt_server = bumper.MQTTServer(mqtt_address, password_file="tests/passwd") await mqtt_server.broker_coro() with LogCapture() as l: @@ -31,10 +31,9 @@ async def test_helperbot_message(): await mqtt_helperbot.Client.publish( msg_topic_name, msg_payload.encode(), hbmqtt.client.QOS_0 ) - try: - await asyncio.wait_for(mqtt_helperbot.Client.deliver_message(), timeout=0.1) - except asyncio.TimeoutError: - pass + + await asyncio.wait_for(mqtt_helperbot.Client.deliver_message(), timeout=0.1) + l.check_present( ( "helperbot", @@ -53,20 +52,19 @@ async def test_helperbot_message(): ) # Check helperbot is connected msg_payload = "{}" msg_topic_name = ( - "iot/p2p/GetWKVer/helper1/bumper/helper1/bot_serial/ls1ok3/wC3g/q/iCmuqp/j" + "iot/p2p/GetWKVer/helperbot/bumper/helperbot/bot_serial/ls1ok3/wC3g/q/iCmuqp/j" ) await mqtt_helperbot.Client.publish( msg_topic_name, msg_payload.encode(), hbmqtt.client.QOS_0 ) - try: - await asyncio.wait_for(mqtt_helperbot.Client.deliver_message(), timeout=0.1) - except asyncio.TimeoutError: - pass + + await asyncio.wait_for(mqtt_helperbot.Client.deliver_message(), timeout=0.1) + l.check_present( ( "helperbot", "DEBUG", - "Send Command - Topic: iot/p2p/GetWKVer/helper1/bumper/helper1/bot_serial/ls1ok3/wC3g/q/iCmuqp/j - Message: {}", + "Send Command - Topic: iot/p2p/GetWKVer/helperbot/bumper/helperbot/bot_serial/ls1ok3/wC3g/q/iCmuqp/j - Message: {}", ) ) # Check send command message was logged l.clear() @@ -80,20 +78,19 @@ async def test_helperbot_message(): ) # Check helperbot is connected msg_payload = '{"ret":"ok","ver":"0.13.5"}' msg_topic_name = ( - "iot/p2p/GetWKVer/bot_serial/ls1ok3/wC3g/helper1/bumper/helper1/p/iCmuqp/j" + "iot/p2p/GetWKVer/bot_serial/ls1ok3/wC3g/helperbot/bumper/helperbot/p/iCmuqp/j" ) await mqtt_helperbot.Client.publish( msg_topic_name, msg_payload.encode(), hbmqtt.client.QOS_0 ) - try: - await asyncio.wait_for(mqtt_helperbot.Client.deliver_message(), timeout=0.1) - except asyncio.TimeoutError: - pass + + await asyncio.wait_for(mqtt_helperbot.Client.deliver_message(), timeout=0.1) + l.check_present( ( "helperbot", "DEBUG", - 'Received Response - Topic: iot/p2p/GetWKVer/bot_serial/ls1ok3/wC3g/helper1/bumper/helper1/p/iCmuqp/j - Message: {"ret":"ok","ver":"0.13.5"}', + 'Received Response - Topic: iot/p2p/GetWKVer/bot_serial/ls1ok3/wC3g/helperbot/bumper/helperbot/p/iCmuqp/j - Message: {"ret":"ok","ver":"0.13.5"}', ) ) # Check received response message was logged l.clear() @@ -107,21 +104,20 @@ async def test_helperbot_message(): ) # Check helperbot is connected msg_payload = "test" msg_topic_name = ( - "iot/p2p/GetWKVer/bot_serial/ls1ok3/wC3g/TESTBAD/bumper/helper1/p/iCmuqp/j" + "iot/p2p/GetWKVer/bot_serial/ls1ok3/wC3g/TESTBAD/bumper/helperbot/p/iCmuqp/j" ) await mqtt_helperbot.Client.publish( msg_topic_name, msg_payload.encode(), hbmqtt.client.QOS_0 ) - try: - await asyncio.wait_for(mqtt_helperbot.Client.deliver_message(), timeout=0.1) - except asyncio.TimeoutError: - pass + + await asyncio.wait_for(mqtt_helperbot.Client.deliver_message(), timeout=0.1) + l.check_present( ( "helperbot", "DEBUG", - "Received Message - Topic: iot/p2p/GetWKVer/bot_serial/ls1ok3/wC3g/TESTBAD/bumper/helper1/p/iCmuqp/j - Message: test", + "Received Message - Topic: iot/p2p/GetWKVer/bot_serial/ls1ok3/wC3g/TESTBAD/bumper/helperbot/p/iCmuqp/j - Message: test", ) ) # Check received message was logged l.clear() @@ -138,10 +134,8 @@ async def test_helperbot_message(): await mqtt_helperbot.Client.publish( msg_topic_name, msg_payload.encode(), hbmqtt.client.QOS_0 ) - try: - await asyncio.wait_for(mqtt_helperbot.Client.deliver_message(), timeout=0.1) - except asyncio.TimeoutError: - pass + + await asyncio.wait_for(mqtt_helperbot.Client.deliver_message(), timeout=0.1) l.check_present( ( @@ -158,20 +152,21 @@ async def test_helperbot_message(): async def test_helperbot_expire_message(): mqtt_address = ("127.0.0.1", 8883) - mqtt_server = bumper.MQTTServer(mqtt_address) + mqtt_server = bumper.MQTTServer(mqtt_address, password_file="tests/passwd") await mqtt_server.broker_coro() with LogCapture("helperbot") as l: # Test broadcast message mqtt_helperbot = bumper.MQTTHelperBot(mqtt_address) + bumper.mqtt_helperbot = mqtt_helperbot await mqtt_helperbot.start_helper_bot() assert ( mqtt_helperbot.Client._connected_state._value == True ) # Check helperbot is connected expire_msg_payload = '{"ret":"ok","ver":"0.13.5"}' - expire_msg_topic_name = "iot/p2p/GetWKVer/bot_serial/ls1ok3/wC3g/helper1/bumper/helper1/p/testgood/j" + expire_msg_topic_name = "iot/p2p/GetWKVer/bot_serial/ls1ok3/wC3g/helperbot/bumper/helperbot/p/testgood/j" currenttime = time.time() mqtt_helperbot.command_responses.append( { @@ -187,7 +182,7 @@ async def test_helperbot_expire_message(): "payload": expire_msg_payload, } in mqtt_helperbot.command_responses # check message is in command_responses - await asyncio.sleep(0.2) + await asyncio.sleep(0.1) mqtt_helperbot.expire_msg_seconds = ( 0.1 ) # Set expire message seconds to 0.1 so we don't wait 10 seconds @@ -197,10 +192,9 @@ async def test_helperbot_expire_message(): msg_topic_name, msg_payload.encode(), hbmqtt.client.QOS_0 ) # Send another message to force get_msg - try: - await asyncio.wait_for(mqtt_helperbot.Client.deliver_message(), timeout=0.1) - except asyncio.TimeoutError: - pass + + await asyncio.wait_for(mqtt_helperbot.Client.deliver_message(), timeout=0.1) + assert { "time": currenttime, @@ -220,14 +214,16 @@ async def test_helperbot_expire_message(): mqtt_helperbot.Client.disconnect() await mqtt_server.broker.shutdown() + async def test_helperbot_sendcommand(): mqtt_address = ("127.0.0.1", 8883) - mqtt_server = bumper.MQTTServer(mqtt_address) + mqtt_server = bumper.MQTTServer(mqtt_address, password_file="tests/passwd") await mqtt_server.broker_coro() mqtt_helperbot = bumper.MQTTHelperBot(mqtt_address) + bumper.mqtt_helperbot = mqtt_helperbot await mqtt_helperbot.start_helper_bot() assert ( mqtt_helperbot.Client._connected_state._value == True @@ -267,7 +263,7 @@ async def test_helperbot_sendcommand(): # Send response beforehand msg_payload = '{"ret":"ok","ver":"0.13.5"}' msg_topic_name = ( - "iot/p2p/GetWKVer/bot_serial/ls1ok3/wC3g/helper1/bumper/helper1/p/testgood/j" + "iot/p2p/GetWKVer/bot_serial/ls1ok3/wC3g/helperbot/bumper/helperbot/p/testgood/j" ) await mqtt_helperbot.Client.publish( msg_topic_name, msg_payload.encode(), hbmqtt.client.QOS_0 @@ -306,7 +302,7 @@ async def test_helperbot_sendcommand(): # Send response beforehand msg_payload = "" msg_topic_name = ( - "iot/p2p/GetLifeSpan/bot_serial/ls1ok3/wC3g/helper1/bumper/helper1/p/testx/q" + "iot/p2p/GetLifeSpan/bot_serial/ls1ok3/wC3g/helperbot/bumper/helperbot/p/testx/q" ) await mqtt_helperbot.Client.publish( msg_topic_name, msg_payload.encode(), hbmqtt.client.QOS_0 @@ -351,7 +347,7 @@ async def test_helperbot_sendcommand(): msg_payload = '{"body":{"code":0,"data":{"area":0,"cid":"111","start":"1569378657","time":6,"type":"auto"},"msg":"ok"},"header":{"fwVer":"1.6.4","hwVer":"0.1.1","pri":1,"ts":"1569380074036","tzm":480,"ver":"0.0.1"}}' msg_topic_name = ( - "iot/p2p/getStats/bot_serial/ls1ok3/wC3g/helper1/bumper/helper1/p/testj/j" + "iot/p2p/getStats/bot_serial/ls1ok3/wC3g/helperbot/bumper/helperbot/p/testj/j" ) await mqtt_helperbot.Client.publish( msg_topic_name, msg_payload.encode(), hbmqtt.client.QOS_0 @@ -368,6 +364,7 @@ async def test_helperbot_sendcommand(): mqtt_helperbot.Client.disconnect() await mqtt_server.broker.shutdown() + async def test_mqttserver(): @@ -378,26 +375,7 @@ async def test_mqttserver(): mqtt_address = ("127.0.0.1", 8883) - mqtt_server = bumper.MQTTServer(mqtt_address) - - mqtt_server.default_config = { - "listeners": { - "default": {"type": "tcp"}, - "tls1": { - "bind": "{}:{}".format(mqtt_address[0], mqtt_address[1]), - "ssl": "on", - "certfile": bumper.server_cert, - "keyfile": bumper.server_key, - }, - }, - "sys_interval": 10, - "auth": { - "allow-anonymous": True, # Set to True to allow anonymous authentication - "password-file": "tests/passwd", # For file auth, set user:hash in passwd file see (https://hbmqtt.readthedocs.io/en/latest/references/hbmqtt.html#configuration-example) - "plugins": ["bumper"], # Bumper plugin provides auth and handling of bots/clients connecting - }, - "topic-check": {"enabled": False}, - } + mqtt_server = bumper.MQTTServer(mqtt_address, password_file="tests/passwd", allow_anonymous=True) await mqtt_server.broker_coro() @@ -463,147 +441,41 @@ async def test_mqttserver(): ) # Check client is disconnected # bad password - try: + with LogCapture() as l: + await test_client.Client.connect( f"mqtts://test-client:notvalid!@{test_client.address[0]}:{test_client.address[1]}/", cafile=bumper.ca_cert, cleansession=True ) - assert ( - test_client.Client._connected_state._value == False - ) # Check client is connected - - except Exception as ae: - pass - - # no username in file - try: + l.check_present( + ("mqttserver", "INFO", "File Authentication Failed - Username: test-client - ClientID: test-file-auth"), + order_matters=False + ) + # no username in file await test_client.Client.connect( f"mqtts://test-client-noexist:notvalid!@{test_client.address[0]}:{test_client.address[1]}/", cafile=bumper.ca_cert, cleansession=True ) - assert ( - test_client.Client._connected_state._value == False - ) # Check client is connected - except Exception as ae: - pass - - - - await asyncio.sleep(0.1) - - await mqtt_server.broker.shutdown() - await asyncio.sleep(0.1) - -async def test_passwordfile_badhash_mqttserver(): - mqtt_address = ("127.0.0.1", 8883) - - mqtt_server = bumper.MQTTServer(mqtt_address) - - mqtt_server.default_config = { - "listeners": { - "default": {"type": "tcp"}, - "tls1": { - "bind": "{}:{}".format(mqtt_address[0], mqtt_address[1]), - "ssl": "on", - "certfile": bumper.server_cert, - "keyfile": bumper.server_key, - }, - }, - "sys_interval": 10, - "auth": { - "allow-anonymous": True, # Set to True to allow anonymous authentication - "password-file": "tests/passwd_bad", # For file auth, set user:hash in passwd file see (https://hbmqtt.readthedocs.io/en/latest/references/hbmqtt.html#configuration-example) - "plugins": ["bumper"], # Bumper plugin provides auth and handling of bots/clients connecting - }, - "topic-check": {"enabled": False}, - } - - await mqtt_server.broker_coro() - await asyncio.sleep(0.1) - - # bad password - try: - test_client = bumper.MQTTHelperBot(mqtt_address) - - await test_client.Client.connect( - f"mqtts://test-client:notvalid!@{test_client.address[0]}:{test_client.address[1]}/", - cafile=bumper.ca_cert, cleansession=True + l.check_present( + ("mqttserver", "INFO", 'File Authentication Failed - No Entry for Username: test-client-noexist - ClientID: test-file-auth'), + order_matters=False ) - - assert ( - test_client.Client._connected_state._value == False - ) # Check client is connected - - - except Exception as ae: - pass await mqtt_server.broker.shutdown() - await asyncio.sleep(0.1) + async def test_nofileauth_mqttserver(): - try: + with LogCapture() as l: mqtt_address = ("127.0.0.1", 8883) - - mqtt_server = bumper.MQTTServer(mqtt_address) - - mqtt_server.default_config = { - "listeners": { - "default": {"type": "tcp"}, - "tls1": { - "bind": "{}:{}".format(mqtt_address[0], mqtt_address[1]), - "ssl": "on", - "certfile": bumper.server_cert, - "keyfile": bumper.server_key, - }, - }, - "sys_interval": 10, - "auth": { - "allow-anonymous": True, # Set to True to allow anonymous authentication - "password-file": "tests/passwd-notfound", # For file auth, set user:hash in passwd file see (https://hbmqtt.readthedocs.io/en/latest/references/hbmqtt.html#configuration-example) - "plugins": ["bumper"], # Bumper plugin provides auth and handling of bots/clients connecting - }, - "topic-check": {"enabled": False}, - } - + mqtt_server = bumper.MQTTServer(mqtt_address, password_file="tests/passwd-notfound") await mqtt_server.broker_coro() + await mqtt_server.broker.shutdown() - except: - pass - - -async def test_passwordfile_opt_missing_mqttserver(): - try: - - mqtt_address = ("127.0.0.1", 8883) - - mqtt_server = bumper.MQTTServer(mqtt_address) - - mqtt_server.default_config = { - "listeners": { - "default": {"type": "tcp"}, - "tls1": { - "bind": "{}:{}".format(mqtt_address[0], mqtt_address[1]), - "ssl": "on", - "certfile": bumper.server_cert, - "keyfile": bumper.server_key, - }, - }, - "sys_interval": 10, - "auth": { - "allow-anonymous": True, # Set to True to allow anonymous authentication - - "plugins": ["bumper"], # Bumper plugin provides auth and handling of bots/clients connecting - }, - "topic-check": {"enabled": False}, - } - - await mqtt_server.broker_coro() - await mqtt_server.broker.shutdown() - - except: - pass + l.check_present( + ("hbmqtt.broker.plugins.bumper", "WARNING", 'Password file tests/passwd-notfound not found'), + order_matters=False + )