diff --git a/bumper.py b/bumper.py index a6ba2ac..11aba3b 100644 --- a/bumper.py +++ b/bumper.py @@ -30,12 +30,21 @@ def main(): xmpp_address = (listen_host, 5223) mqtt_address = (listen_host, 8883) - xmpp_server = bumper.XMPPServer(xmpp_address) - mqtt_server = bumper.MQTTServer(mqtt_address,bumper_bots=bumper.bumper_bots_var,bumper_clients=bumper.bumper_clients_var,remove_clients=bumper.bumper_removeclients_var) + xmpp_server = bumper.XMPPServer(xmpp_address, bumper_users=bumper.bumper_users_var, bumper_bots=bumper.bumper_bots_var,bumper_clients=bumper.bumper_clients_var) + mqtt_server = bumper.MQTTServer(mqtt_address, bumper_bots=bumper.bumper_bots_var,bumper_clients=bumper.bumper_clients_var) mqtt_helperbot = bumper.MQTTHelperBot(mqtt_address, bumper_bots=bumper.bumper_bots_var,bumper_clients=bumper.bumper_clients_var) - conf_server = bumper.ConfServer(conf_address_443, usessl=True, bumper_bots=bumper.bumper_bots_var,bumper_clients=bumper.bumper_clients_var, remove_clients=bumper.bumper_removeclients_var,helperbot=mqtt_helperbot) - conf_server_2 = bumper.ConfServer(conf_address_8007, usessl=False, bumper_bots=bumper.bumper_bots_var,bumper_clients=bumper.bumper_clients_var, helperbot=mqtt_helperbot,remove_clients=bumper.bumper_removeclients_var) + conf_server = bumper.ConfServer(conf_address_443, usessl=True,bumper_users=bumper.bumper_users_var, bumper_bots=bumper.bumper_bots_var,bumper_clients=bumper.bumper_clients_var,helperbot=mqtt_helperbot) + conf_server_2 = bumper.ConfServer(conf_address_8007, usessl=False,bumper_users=bumper.bumper_users_var, bumper_bots=bumper.bumper_bots_var,bumper_clients=bumper.bumper_clients_var, helperbot=mqtt_helperbot) + #add user + users = bumper.bumper_users_var.get() + user1 = bumper.BumperUser('user1') + user1.add_device('user_phone') + user1.add_bot('bot_id') + users.append(user1) + bumper.bumper_users_var.set(users) + + # start xmpp server on port 5223 (sync) xmpp_server.run(run_async=True) #Start in new thread @@ -58,17 +67,19 @@ def main(): time.sleep(0.25) # WIP: Remove clients that have disconnected - remove_clients = bumper.bumper_removeclients_var.get() - if len(remove_clients) > 0: - for uid in remove_clients: - if uid != "": - xmpp_server.remove_client_byuid(uid) #Remove clients from xmpp server - remove_clients.remove(uid) + # remove_clients = bumper.bumper_removeclients_var.get() + # if len(remove_clients) > 0: + # for uid in remove_clients: + # if uid != "": + # xmpp_server.remove_client_byuid(uid) #Remove clients from xmpp server + # remove_clients.remove(uid) - bumper.bumper_removeclients_var.set(remove_clients) + # bumper.bumper_removeclients_var.set(remove_clients) - except KeyboardInterrupt: + except KeyboardInterrupt: + bumper.bumperlog.info("Bumper Exiting - Keyboard Interrupt") print("Bumper Exiting") + exit(1) if __name__ == "__main__": main() \ No newline at end of file diff --git a/bumper/__init__.py b/bumper/__init__.py index e38cf5b..b39671c 100644 --- a/bumper/__init__.py +++ b/bumper/__init__.py @@ -8,10 +8,12 @@ import asyncio import contextvars import time import logging +from base64 import b64decode, b64encode +bumper_users_var = contextvars.ContextVar('bumper_users', default=[]) bumper_clients_var = contextvars.ContextVar('bumper_clients', default=[]) bumper_bots_var = contextvars.ContextVar('bumper_bots', default=[]) -bumper_removeclients_var = contextvars.ContextVar('bumper_removeclients', default=[]) + ca_cert = './certs/CA/cacert.pem' server_cert = './certs/cert.pem' server_key = './certs/key.pem' @@ -35,6 +37,47 @@ def get_milli_time(timetoconvert): return int(round(timetoconvert * 1000)) +class BumperUser(object): + def __init__(self,userid=""): + self.userid = userid + self.devices = [] + self.tokens = [] + self.authcodes = [] + self.bots = [] + + def add_device(self, devid): + if not devid in self.devices: + self.devices.append(devid) + + def remove_device(self, devid): + if devid in self.devices: + self.devices.remove(devid) + + + def add_token(self, token): + if not token in self.tokens: + self.tokens.append(token) + + def revoke_token(self, token): + if token in self.tokens: + self.tokens.remove(token) + + def add_authcode(self, authcode): + if not authcode in self.authcodes: + self.authcodes.append(authcode) + + def revoke_authcode(self, authcode): + if authcode in self.authcodes: + self.authcodes.remove(authcode) + + def add_bot(self, botdid): + if not botdid in self.bots: + self.bots.append(botdid) + + def remove_bot(self, botdid): + if botdid in self.bots: + self.bots.remove(botdid) + class VacBotDevice(object): def __init__(self,did="", vac_bot_device_class="",resource="" , name="", nick="", company="eco-ng"): self.vac_bot_device_class = vac_bot_device_class @@ -48,12 +91,11 @@ class VacBotDevice(object): return {"class": self.vac_bot_device_class, "company": self.company, "did": self.did, "name": self.name, "nick": self.nick, "resource": self.resource} -class VacBotUser(object): +class VacBotClient(object): def __init__(self,userid="",realm="",token=""): self.userid = userid self.realm = realm self.resource = token - def asdict(self): return {"userid": self.userid,"realm": self.realm,"resource": self.resource} diff --git a/bumper/confserver.py b/bumper/confserver.py index 501ca1f..ebebe5c 100644 --- a/bumper/confserver.py +++ b/bumper/confserver.py @@ -10,6 +10,7 @@ from datetime import datetime, timedelta import asyncio import contextvars from aiohttp import web +import uuid class aiohttp_filter(logging.Filter): @@ -32,10 +33,10 @@ class ConfServer(): bumper_clients = contextvars.ContextVar bumper_bots = contextvars.ContextVar - def __init__(self, address, usessl=False, bumper_bots=contextvars.ContextVar, bumper_clients=contextvars.ContextVar, remove_clients=contextvars.ContextVar,helperbot=None): + def __init__(self, address, usessl=False, bumper_users=contextvars.ContextVar, bumper_bots=contextvars.ContextVar, bumper_clients=contextvars.ContextVar, helperbot=None): + self.bumper_users = bumper_users self.bumper_bots = bumper_bots - self.bumper_clients = bumper_clients - self.remove_clients = remove_clients + self.bumper_clients = bumper_clients self.helperbot = helperbot self.usessl = usessl self.address = address @@ -82,7 +83,7 @@ class ConfServer(): app.add_routes([ web.get('', self.handle_base), 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_checkLogin), + web.get('/{apiversion}/private/{country}/{language}/{devid}/{apptype}/{appversion}/{devtype}/{aid}/user/checkLogin', self.handle_login), 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('/{apiversion}/private/{country}/{language}/{devid}/{apptype}/{appversion}/{devtype}/{aid}/user/checkAgreement', self.handle_checkAgreement), @@ -96,6 +97,9 @@ class ConfServer(): web.post('/lookup.do', self.handle_lookup), ]) + #Direct register from app: + #/{apiversion}/private/{country}/{language}/{devid}/{apptype}/{appversion}/{devtype}/{aid}/user/directRegister + runner = web.AppRunner(app) @@ -130,61 +134,65 @@ class ConfServer(): except Exception as e: confserverlog.exception('{}'.format(e)) - async def handle_login(self, request): + async def handle_login(self, request): + try: - #Could implement basic auth if you wanted, or just accept anything - countrycode = request.match_info.get('country', "us") + user_devid = request.match_info.get('devid', "") + countrycode = request.match_info.get('country', "us") + if not user_devid == "": #Performing basic "auth" using devid, super insecure + users = bumper.bumper_users_var.get() + for user in users: + if user_devid in user.devices: + tmpaccesstoken = '' + if 'checkLogin' in request.path: + if request.query['accessToken'] in user.tokens and request.query['uid'] == "fuid_{}".format(user.userid): + tmpaccesstoken = request.query['accessToken'] + else: + if tmpaccesstoken == '': + tmpaccesstoken = uuid.uuid4().hex + user.add_token(tmpaccesstoken) + + body = { + "code": "0000", + "data": { + "accessToken": tmpaccesstoken, #Random chars 32 length + "country": countrycode, + "email": "null@null.com", + "uid": "fuid_{}".format(user.userid), + "username": "fusername_{}".format(user.userid), + }, + "msg": "操作成功", + "time": bumper.get_milli_time(time.time()) + } + bumper.bumper_users_var.set(users) + return web.json_response(body) + body = { - "code": "0000", - "data": { - "accessToken": "tempaccesstoken", #Random chars 32 length - "country": countrycode, - "email": "null@null.com", - "uid": "fuid_{}".format(''.join(random.sample(string.ascii_letters,6))), #Date(14)_RandomChars(32) - "username": "fusername_{}".format(''.join(random.sample(string.ascii_letters,6))) #Random chars 8 - }, - "msg": "操作成功", + "code": "1005", + "data": None, + "msg": "当前密码错误", "time": bumper.get_milli_time(time.time()) - } - + } + return web.json_response(body) + except Exception as e: confserverlog.exception('{}'.format(e)) - async def handle_checkLogin(self, request): - try: - # The app seems to remember it's last uid and accessToken - # If these don't match, it fails - countrycode = request.match_info.get('country', "us") - body = { - "code": "0000", - "data": { - "accessToken": "tempaccesstoken", #Random chars 32 length - "country": countrycode, - "email": "null@null.com", - "uid": "fuid_{}".format(''.join(random.sample(string.ascii_letters,6))), #Date(14)_RandomChars(32) - "username": "fusername_{}".format(''.join(random.sample(string.ascii_letters,6))) #Random chars 8 - }, - "msg": "操作成功", - "time": bumper.get_milli_time(time.time()) - } - - return web.json_response(body) - - except Exception as e: - confserverlog.exception('{}'.format(e)) async def handle_logout(self, request): - try: - uid = request.query['uid'] - body = {"code": "0000","data": None,"msg": "操作成功", "time": bumper.get_milli_time(time.time())} - # QUERY String - # 'uid=fuid_CUOVIn&accessToken=tempaccesstoken&requestId=e584de79f9cca854df6fb3352c6893b6&authTimespan=1550168440575&authTimeZone=GMT-5&authAppkey=eJUWrzRv34qFSaYk&authSign=14e38f95c7316e111c5815cf15f0f972' - if not uid == "": - remove_clients = self.remove_clients.get() - remove_clients.append(request.query['uid']) - self.remove_clients.set(remove_clients) + try: + user_devid = request.match_info.get('devid', "") + if not user_devid == "": + users = bumper.bumper_users_var.get() + for user in users: + if user_devid in user.devices: + if request.query['uid'] == "fuid_{}".format(user.userid) and request.query['accessToken'] in user.tokens: + user.revoke_token(request.query['accessToken']) + bumper.bumper_users_var.set(users) + + body = {"code": "0000","data": None,"msg": "操作成功", "time": bumper.get_milli_time(time.time())} return web.json_response(body) @@ -193,19 +201,37 @@ class ConfServer(): async def handle_getAuthCode(self, request): try: - countrycode = request.match_info.get('country', "us") - body = { - "code": "0000", - "data": { - "authCode": "{}_tempauthcode".format(countrycode), #countrycode_randomchars(32) - "ecovacsUid": "fuid_{}".format(''.join(random.sample(string.ascii_letters,6))) #Date(14)_RandomChars(32) - }, - "msg": "操作成功", - "time": bumper.get_milli_time(time.time()) - } - - return web.json_response(body) + + user_devid = request.match_info.get('devid', "") + if not user_devid == "": + users = bumper.bumper_users_var.get() + for user in users: + if user_devid in user.devices and request.query['accessToken'] in user.tokens: + countrycode = request.match_info.get('country', "us") + tmpauthcode = "{}_{}".format(countrycode,uuid.uuid4().hex) + user.add_authcode(tmpauthcode) + + body = { + "code": "0000", + "data": { + "authCode": tmpauthcode, + "ecovacsUid": request.query['uid'] + }, + "msg": "操作成功", + "time": bumper.get_milli_time(time.time()) + } + bumper.bumper_users_var.set(users) + return web.json_response(body) + body = { + "code": "1005", + "data": None, + "msg": "当前密码错误", + "time": bumper.get_milli_time(time.time()) + } + + return web.json_response(body) + except Exception as e: confserverlog.exception('{}'.format(e)) @@ -271,7 +297,6 @@ class ConfServer(): async def handle_getProductIotMap(self, request): try: - #json_body = json.loads(await request.text()) body = {"code":0,"data":[{"classid":"dl8fht","product":{"_id":"5acb0fa87c295c0001876ecf","name":"DEEBOT 600 Series","icon":"5acc32067c295c0001876eea","UILogicId":"dl8fht","ota":False,"iconUrl":"https://portal-ww.ecouser.net/api/pim/file/get/5acc32067c295c0001876eea"}},{"classid":"02uwxm","product":{"_id":"5ae1481e7ccd1a0001e1f69e","name":"DEEBOT OZMO Slim10 Series","icon":"5b1dddc48bc45700014035a1","UILogicId":"02uwxm","ota":False,"iconUrl":"https://portal-ww.ecouser.net/api/pim/file/get/5b1dddc48bc45700014035a1"}},{"classid":"y79a7u","product":{"_id":"5b04c0227ccd1a0001e1f6a8","name":"DEEBOT OZMO 900","icon":"5b04c0217ccd1a0001e1f6a7","UILogicId":"y79a7u","ota":True,"iconUrl":"https://portal-ww.ecouser.net/api/pim/file/get/5b04c0217ccd1a0001e1f6a7"}},{"classid":"jr3pqa","product":{"_id":"5b43077b8bc457000140363e","name":"DEEBOT 711","icon":"5b5ac4cc8d5a56000111e769","UILogicId":"jr3pqa","ota":True,"iconUrl":"https://portal-ww.ecouser.net/api/pim/file/get/5b5ac4cc8d5a56000111e769"}},{"classid":"uv242z","product":{"_id":"5b5149b4ac0b87000148c128","name":"DEEBOT 710","icon":"5b5ac4e45f21100001882bb9","UILogicId":"uv242z","ota":True,"iconUrl":"https://portal-ww.ecouser.net/api/pim/file/get/5b5ac4e45f21100001882bb9"}},{"classid":"ls1ok3","product":{"_id":"5b6561060506b100015c8868","name":"DEEBOT 900 Series","icon":"5ba4a2cb6c2f120001c32839","UILogicId":"ls1ok3","ota":True,"iconUrl":"https://portal-ww.ecouser.net/api/pim/file/get/5ba4a2cb6c2f120001c32839"}}]} return web.json_response(body) @@ -297,13 +322,18 @@ class ConfServer(): elif service == 'EcoUpdate': body = {"result":"ok","ip":"47.88.66.164","port":8005} elif todo == 'loginByItToken': - body = { - "resource": postbody["resource"], - "result": "ok", - "todo": "result", - "token": postbody["token"], #RandomChar(32) - "userId": postbody["userId"] #RandomChar(16) - } + + users = bumper.bumper_users_var.get() + for user in users: + if postbody['userId'] == "fuid_{}".format(user.userid) and postbody['token'] in user.authcodes: + body = { + "resource": postbody["resource"], + "result": "ok", + "todo": "result", + "token": postbody["token"], + "userId": postbody["userId"] + } + elif todo == 'GetDeviceList': active_bots = self.bumper_bots.get() body = { diff --git a/bumper/mqttserver.py b/bumper/mqttserver.py index 39e610b..e7ee992 100644 --- a/bumper/mqttserver.py +++ b/bumper/mqttserver.py @@ -174,12 +174,11 @@ class MQTTServer(): except Exception as e: mqttserverlog.exception('{}'.format(e)) - def __init__(self, address, bumper_bots=contextvars.ContextVar, bumper_clients=contextvars.ContextVar,remove_clients=contextvars.ContextVar): + def __init__(self, address, bumper_bots=contextvars.ContextVar, bumper_clients=contextvars.ContextVar): try: self.bumper_bots = bumper_bots self.bumper_clients = bumper_clients - self.remove_clients = remove_clients self.mqttserverthread = None self.address = address @@ -216,7 +215,6 @@ class MQTTServer(): 'clients':{ 'connected_bots': self.bumper_bots, 'connected_clients': self.bumper_clients, - 'remove_clients': self.remove_clients }, } @@ -285,7 +283,7 @@ class BumperMQTTServer_Plugin: self.clients['connected_bots'].set(connected_bots) else: tmpuserdetail = str(didsplit[1]).split("/") - newuser = bumper.VacBotUser() + newuser = bumper.VacBotClient() newuser.userid = didsplit[0] newuser.realm = tmpuserdetail[0] newuser.resource = tmpuserdetail[1] @@ -314,7 +312,7 @@ class BumperMQTTServer_Plugin: #mqttserverlog.debug('%s disconnected' % client_id) connected_bots = self.clients['connected_bots'].get() connected_clients = self.clients['connected_clients'].get() - remove_clients = self.clients['remove_clients'].get() + #remove_clients = self.clients['remove_clients'].get() didsplit = str(client_id).split("@") #If the did is in the list, remove it for bot in connected_bots: diff --git a/bumper/xmppserver.py b/bumper/xmppserver.py index e0a3228..4c1f800 100644 --- a/bumper/xmppserver.py +++ b/bumper/xmppserver.py @@ -4,6 +4,7 @@ from threading import Thread import sys, socket, threading, re, time, logging, uuid, xml.etree.ElementTree as ET import base64 import ssl +import contextvars import bumper xmppserverlog = logging.getLogger("xmppserver") @@ -15,9 +16,12 @@ class XMPPServer(): clients = [] exit_flag = False - def __init__(self, address): + def __init__(self, address, bumper_users=contextvars.ContextVar, bumper_bots=contextvars.ContextVar, bumper_clients=contextvars.ContextVar): # Initialize bot server self.address = address + self.bumper_users = bumper_users + self.bumper_bots = bumper_bots + self.bumper_clients = bumper_clients def run(self, run_async=False): @@ -86,9 +90,7 @@ class XMPPServer(): self.disconnect() xmppserverlog.info('disconnecting') - self.socket.close() - - + self.socket.close() def disconnect(self): try: @@ -339,8 +341,8 @@ class Client(threading.Thread): elif 'resource' in aitem.tag: self.clientresource = aitem.text - - if True: + + if self.check_authcode(self.uid, password): #Client authenticated, move to next state self._set_state('INIT') @@ -364,6 +366,13 @@ class Client(threading.Thread): except Exception as e: xmppserverlog.exception('{}'.format(e)) + def check_authcode(self, uid, authcode): + users = bumper.bumper_users_var.get() + for user in users: + if uid == "fuid_{}".format(user.userid) and authcode in user.authcodes: + return True + + return False def _handle_sasl_auth(self, data): try: @@ -375,18 +384,18 @@ class Client(threading.Thread): resource = saslauth[1] self.clientresource = resource authcode = saslauth[2] - - if authcode == "us_tempauthcode": #Handle auth + + if self.check_authcode(self.uid, authcode): #Send response self.send('') #Success #Client authenticated, move to next state self._set_state('INIT') - + else: #Failed to authenticate self.send('') #Fail - + except ET.ParseError as e: if "no element found" in e.msg: xmppserverlog.debug('xml parse error - {} - {} - this is common with ecovac protocol'.format(data.decode('utf-8'), e))