D901 #2

Merged
bmartin5692 merged 34 commits from D901 into dev 2019-02-22 05:37:44 +01:00
6 changed files with 306 additions and 166 deletions
Showing only changes of commit 0a5d93b099 - Show all commits

View file

@ -149,9 +149,13 @@ So far two protocols have been identified that various models of EcoVacs robots
---- ----
### Login/Authentication/RestAPI ### Login/Authentication/RestAPI
***Bumper*** provides a fully simulated central server that handles login/authentication for the app/clients. At this time no authentication layer is implemented and you can use any e-mail/password when logging in. Future versions should add additional options here for security. ***Bumper*** provides a fully simulated central server that handles login/authentication for the app/clients.
This means however, that once a robot has been configured to access your WiFi it never needs to communicate with EcoVac's servers again. The EcoVacs app encrypts the username/password with the public key of EcoVacs when authenticating. Since we don't have the private key to decrypt, there is no way to provide true security and authentication.
Future versions may add additional options here for security.
Bots have no authentication and once a robot has been configured to access your WiFi it never needs to communicate with EcoVac's servers again.
---- ----
### XMPP ### XMPP

View file

@ -37,13 +37,12 @@ def main():
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) 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 #add user
users = bumper.bumper_users_var.get() # users = bumper.bumper_users_var.get()
user1 = bumper.BumperUser('user1') # user1 = bumper.BumperUser('user1')
user1.add_device('devid') # user1.add_device('devid')
user1.add_bot('bot_did') # user1.add_bot('bot_did')
users.append(user1) # users.append(user1)
bumper.bumper_users_var.set(users) # bumper.bumper_users_var.set(users)
# start xmpp server on port 5223 (sync) # start xmpp server on port 5223 (sync)
xmpp_server.run(run_async=True) #Start in new thread xmpp_server.run(run_async=True) #Start in new thread

View file

@ -18,6 +18,8 @@ ca_cert = './certs/CA/cacert.pem'
server_cert = './certs/cert.pem' server_cert = './certs/cert.pem'
server_key = './certs/key.pem' server_key = './certs/key.pem'
use_auth = False
#Logs #Logs
bumperlog = logging.getLogger("bumper") bumperlog = logging.getLogger("bumper")
confserverlog = logging.getLogger("confserver") confserverlog = logging.getLogger("confserver")
@ -105,12 +107,51 @@ class VacBotClient(object):
return {"userid": self.userid,"realm": self.realm,"resource": self.resource} return {"userid": self.userid,"realm": self.realm,"resource": self.resource}
def check_authcode(uid, authcode): def check_authcode(uid, authcode):
users = bumper_users_var.get() users = bumper_users_var.get()
for user in users: for user in users:
if uid == "fuid_{}".format(user.userid) and authcode in user.authcodes: if uid == "fuid_{}".format(user.userid) and authcode in user.authcodes:
return True return True
return False
def add_bot(sn, did, devclass, resource):
newbot = VacBotDevice()
newbot.did = did
newbot.name = sn
newbot.vac_bot_device_class = devclass
newbot.resource = resource
bots = bumper_bots_var.get()
existingbot = False
for bot in bots:
if bot.did == newbot.did:
existingbot = True
if existingbot == False:
bots.append(newbot)
bumperlog.info("new bot added SN: {} DID: {}".format(newbot.name, newbot.did))
bumper_bots_var.set(bots)
def add_client(userid, realm, resource):
newclient = VacBotClient()
newclient.userid = userid
newclient.realm = realm
newclient.resource = resource
clients = bumper_clients_var.get()
existingclient = False
for client in clients:
if client.userid == newclient.userid:
existingclient = True
if existingclient == False:
clients.append(newclient)
bumperlog.info("new client added {}".format(newclient.userid))
bumper_clients_var.set(clients)
return False
RETURN_API_SUCCESS = "0000" RETURN_API_SUCCESS = "0000"
ERR_ACTIVATE_TOKEN_TIMEOUT = "1006" ERR_ACTIVATE_TOKEN_TIMEOUT = "1006"

View file

@ -135,72 +135,133 @@ class ConfServer():
confserverlog.exception('{}'.format(e)) confserverlog.exception('{}'.format(e))
async def handle_login(self, request): async def handle_login(self, request):
try: try:
user_devid = request.match_info.get('devid', "") user_devid = request.match_info.get('devid', "")
countrycode = request.match_info.get('country', "us") countrycode = request.match_info.get('country', "us")
if not user_devid == "": #Performing basic "auth" using devid, super insecure confserverlog.info('client with devid {} attempting login'.format(user_devid))
users = self.bumper_users.get() if bumper.use_auth:
for user in users: if not user_devid == "": #Performing basic "auth" using devid, super insecure
if user_devid in user.devices: users = self.bumper_users.get()
tmpaccesstoken = '' for user in users:
if 'checkLogin' in request.path: if user_devid in user.devices:
if request.query['accessToken'] in user.tokens and request.query['uid'] == "fuid_{}".format(user.userid): tmpaccesstoken = ''
tmpaccesstoken = request.query['accessToken'] if 'checkLogin' in request.path:
body = { if request.query['accessToken'] in user.tokens and request.query['uid'] == "fuid_{}".format(user.userid):
"code": bumper.RETURN_API_SUCCESS, tmpaccesstoken = request.query['accessToken']
"data": { body = {
"accessToken": tmpaccesstoken, #Random chars 32 length "code": bumper.RETURN_API_SUCCESS,
"country": countrycode, "data": {
"email": "null@null.com", "accessToken": tmpaccesstoken, #Random chars 32 length
"uid": "fuid_{}".format(user.userid), "country": countrycode,
"username": "fusername_{}".format(user.userid), "email": "null@null.com",
}, "uid": "fuid_{}".format(user.userid),
"msg": "操作成功", "username": "fusername_{}".format(user.userid),
},
"msg": "操作成功",
"time": bumper.get_milli_time(time.time())
}
else:
body = {
"code": bumper.ERR_TOKEN_INVALID,
"data": None,
"msg": "当前密码错误",
"time": bumper.get_milli_time(time.time()) "time": bumper.get_milli_time(time.time())
} }
else: else:
if tmpaccesstoken == '':
tmpaccesstoken = uuid.uuid4().hex
user.add_token(tmpaccesstoken)
body = { body = {
"code": bumper.ERR_TOKEN_INVALID, "code": bumper.RETURN_API_SUCCESS,
"data": None, "data": {
"msg": "当前密码错误", "accessToken": tmpaccesstoken, #Random chars 32 length
"time": bumper.get_milli_time(time.time()) "country": countrycode,
} "email": "null@null.com",
else: "uid": "fuid_{}".format(user.userid),
if tmpaccesstoken == '': "username": "fusername_{}".format(user.userid),
tmpaccesstoken = uuid.uuid4().hex },
user.add_token(tmpaccesstoken) "msg": "操作成功",
"time": bumper.get_milli_time(time.time())
}
self.bumper_users.set(users)
body = { return web.json_response(body)
"code": bumper.RETURN_API_SUCCESS,
"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())
}
self.bumper_users.set(users)
return web.json_response(body) body = {
"code": bumper.ERR_USER_NOT_ACTIVATED,
body = { "data": None,
"code": bumper.ERR_USER_NOT_ACTIVATED, "msg": "当前密码错误",
"data": None, "time": bumper.get_milli_time(time.time())
"msg": "当前密码错误", }
"time": bumper.get_milli_time(time.time())
}
return web.json_response(body)
return web.json_response(body)
else:
return web.json_response(self._auth_any(user_devid, countrycode, request))
except Exception as e: except Exception as e:
confserverlog.exception('{}'.format(e)) confserverlog.exception('{}'.format(e))
def _auth_any(self, devid, country, request):
try:
user_devid = devid
countrycode = country
tmpaccesstoken = ''
users = self.bumper_users.get()
bots = self.bumper_bots.get()
if len(users) > 0:
tmpuser = users[0]
tmpuser.add_device(user_devid)
else:
tmpuser = bumper.BumperUser('tmpuser')
users.append(tmpuser)
tmpuser.add_device(user_devid)
for bot in bots:
tmpuser.add_bot(bot.did)
if 'checkLogin' in request.path:
tmpaccesstoken = request.query['accessToken']
tmpuser.add_token(tmpaccesstoken)
body = {
"code": bumper.RETURN_API_SUCCESS,
"data": {
"accessToken": tmpaccesstoken, #Random chars 32 length
"country": countrycode,
"email": "null@null.com",
"uid": "fuid_{}".format(tmpuser.userid),
"username": "fusername_{}".format(tmpuser.userid),
},
"msg": "操作成功",
"time": bumper.get_milli_time(time.time())
}
else:
if tmpaccesstoken == '':
tmpaccesstoken = uuid.uuid4().hex
tmpuser.add_token(tmpaccesstoken)
body = {
"code": bumper.RETURN_API_SUCCESS,
"data": {
"accessToken": tmpaccesstoken, #Random chars 32 length
"country": countrycode,
"email": "null@null.com",
"uid": "fuid_{}".format(tmpuser.userid),
"username": "fusername_{}".format(tmpuser.userid),
},
"msg": "操作成功",
"time": bumper.get_milli_time(time.time())
}
self.bumper_users.set(users)
return body
except Exception as e:
confserverlog.exception('{}'.format(e))
async def handle_logout(self, request): async def handle_logout(self, request):
try: try:
user_devid = request.match_info.get('devid', "") user_devid = request.match_info.get('devid', "")
@ -225,23 +286,24 @@ class ConfServer():
user_devid = request.match_info.get('devid', "") user_devid = request.match_info.get('devid', "")
if not user_devid == "": if not user_devid == "":
users = self.bumper_users.get() users = self.bumper_users.get()
for user in users: if len(users) > 0:
if user_devid in user.devices and request.query['accessToken'] in user.tokens: for user in users:
countrycode = request.match_info.get('country', "us") if user_devid in user.devices and request.query['accessToken'] in user.tokens:
tmpauthcode = "{}_{}".format(countrycode,uuid.uuid4().hex) countrycode = request.match_info.get('country', "us")
user.add_authcode(tmpauthcode) tmpauthcode = "{}_{}".format(countrycode,uuid.uuid4().hex)
user.add_authcode(tmpauthcode)
body = { body = {
"code": bumper.RETURN_API_SUCCESS, "code": bumper.RETURN_API_SUCCESS,
"data": { "data": {
"authCode": tmpauthcode, "authCode": tmpauthcode,
"ecovacsUid": request.query['uid'] "ecovacsUid": request.query['uid']
}, },
"msg": "操作成功", "msg": "操作成功",
"time": bumper.get_milli_time(time.time()) "time": bumper.get_milli_time(time.time())
} }
self.bumper_users.set(users) self.bumper_users.set(users)
return web.json_response(body) return web.json_response(body)
body = { body = {
"code": bumper.ERR_TOKEN_INVALID, "code": bumper.ERR_TOKEN_INVALID,
@ -413,11 +475,17 @@ class ConfServer():
try: try:
json_body = json.loads(await request.text()) json_body = json.loads(await request.text())
randomid = ''.join(random.sample(string.ascii_letters,6)) randomid = ''.join(random.sample(string.ascii_letters,6))
retcmd = await self.helperbot.send_command(json_body, randomid) bots = self.bumper_bots.get()
body = retcmd for bot in bots:
if bot.did == json_body['toId'] and bot.mqtt_connection == True:
confserverlog.debug("\r\n POST: {} \r\n Response: {}".format(json_body,body)) retcmd = await self.helperbot.send_command(json_body, randomid)
return web.json_response(body) body = retcmd
confserverlog.debug("\r\n POST: {} \r\n Response: {}".format(json_body,body))
return web.json_response(body)
else:
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: except Exception as e:
confserverlog.exception('{}'.format(e)) confserverlog.exception('{}'.format(e))

View file

@ -285,50 +285,35 @@ class BumperMQTTServer_Plugin:
#If this isn't a fake user (fuid) then add as a bot #If this isn't a fake user (fuid) then add as a bot
if not (str(didsplit[0]).startswith("fuid") or str(didsplit[0]).startswith("helper")): if not (str(didsplit[0]).startswith("fuid") or str(didsplit[0]).startswith("helper")):
tmpbotdetail = str(didsplit[1]).split("/") tmpbotdetail = str(didsplit[1]).split("/")
newbot = bumper.VacBotDevice() bumper.add_bot(username, didsplit[0], tmpbotdetail[0], tmpbotdetail[1])
newbot.did = didsplit[0] mqttserverlog.debug("new bot authenticated SN: {} DID: {}".format(username, didsplit[0]))
newbot.name = username
newbot.vac_bot_device_class = tmpbotdetail[0]
newbot.resource = tmpbotdetail[1]
existingbot = False
for bot in bumper_bots:
if bot.did == newbot.did:
existingbot = True
if existingbot == False:
bumper_bots.append(newbot)
mqttserverlog.info("new bot authenticated {}".format(newbot.name))
self.bumper_config['bumper_bots'].set(bumper_bots)
authenticated = True authenticated = True
else: else:
if didsplit[0] == "helper1": tmpclientdetail = str(didsplit[1]).split("/")
userid = didsplit[0]
realm = tmpclientdetail[0]
resource = tmpclientdetail[1]
if userid == "helper1":
authenticated = True authenticated = True
elif bumper.check_authcode(didsplit[0], password):
tmpclientdetail = str(didsplit[1]).split("/")
newclient = bumper.VacBotClient()
newclient.userid = didsplit[0]
newclient.realm = tmpclientdetail[0]
newclient.resource = tmpclientdetail[1]
existingclient = False
for client in bumper_clients:
if client.userid == newclient.userid:
existingclient = True
if existingclient == False:
bumper_clients.append(newclient)
mqttserverlog.info("new client authenticated {}".format(newclient.userid))
self.bumper_config['bumper_clients'].set(bumper_clients)
authenticated = True
else: else:
authenticated = False auth = False
if bumper.check_authcode(didsplit[0], password):
auth = True
elif bumper.use_auth == False:
auth = True
if auth:
bumper.add_client(userid, realm, resource)
mqttserverlog.debug("client authenticated {}".format(userid))
authenticated = True
else:
authenticated = False
except KeyError: except KeyError:
self.context.logger.warning("Session informations not available") self.context.logger.warning("Session information not available")
authenticated = False authenticated = False
return authenticated return authenticated
@ -343,13 +328,13 @@ class BumperMQTTServer_Plugin:
for bot in bumper_bots: for bot in bumper_bots:
if didsplit[0] == bot.did: if didsplit[0] == bot.did:
bot.mqtt_connection = True bot.mqtt_connection = True
mqttserverlog.info("bot connected {}".format(bot.did)) #mqttserverlog.info("bot connected {}".format(bot.did))
self.bumper_config['bumper_bots'].set(bumper_bots) self.bumper_config['bumper_bots'].set(bumper_bots)
for client in bumper_clients: for client in bumper_clients:
if didsplit[0] == client.userid and client.userid != 'helper1': if didsplit[0] == client.userid and client.userid != 'helper1':
client.mqtt_connection = True client.mqtt_connection = True
mqttserverlog.info("client connected {}".format(client.userid)) #mqttserverlog.info("client connected {}".format(client.userid))
self.bumper_config['bumper_clients'].set(bumper_clients) self.bumper_config['bumper_clients'].set(bumper_clients)
except Exception as e: except Exception as e:
@ -366,13 +351,13 @@ class BumperMQTTServer_Plugin:
for bot in bumper_bots: for bot in bumper_bots:
if didsplit[0] == bot.did: if didsplit[0] == bot.did:
bot.mqtt_connection = False bot.mqtt_connection = False
mqttserverlog.info("bot disconnected {}".format(bot.did)) #mqttserverlog.info("bot disconnected {}".format(bot.did))
self.bumper_config['bumper_bots'].set(bumper_bots) self.bumper_config['bumper_bots'].set(bumper_bots)
for client in bumper_clients: for client in bumper_clients:
if didsplit[0] == client.userid and client.userid != 'helper1': if didsplit[0] == client.userid and client.userid != 'helper1':
client.mqtt_connection = False client.mqtt_connection = False
mqttserverlog.info("client disconnected {}".format(client.userid)) #mqttserverlog.info("client disconnected {}".format(client.userid))
self.bumper_config['bumper_clients'].set(bumper_clients) self.bumper_config['bumper_clients'].set(bumper_clients)
except Exception as e: except Exception as e:

View file

@ -185,14 +185,14 @@ class Client(threading.Thread):
for bot in bumper_bots: for bot in bumper_bots:
if self.uid == bot.did: if self.uid == bot.did:
bot.xmpp_connection = False bot.xmpp_connection = False
xmppserverlog.info("bot disconnected {}".format(bot.did)) #xmppserverlog.info("bot disconnected {}".format(bot.did))
self.bumper_bots.set(bumper_bots) self.bumper_bots.set(bumper_bots)
for client in bumper_clients: for client in bumper_clients:
if self.uid == client.userid and client.userid != 'helper1': if self.uid == client.userid and client.userid != 'helper1':
client.xmpp_connection = False client.xmpp_connection = False
xmppserverlog.info("client disconnected {}".format(client.userid)) #xmppserverlog.info("client disconnected {}".format(client.userid))
self.bumper_clients.set(bumper_clients) self.bumper_clients.set(bumper_clients)
#xmppserverlog.debug('client {} with resource {} disconnecting'.format(self.address, self.clientresource)) #xmppserverlog.debug('client {} with resource {} disconnecting'.format(self.address, self.clientresource))
@ -356,26 +356,17 @@ class Client(threading.Thread):
elif 'password' in aitem.tag: elif 'password' in aitem.tag:
password = aitem.text.split("/")[2] password = aitem.text.split("/")[2]
authcode = password
elif 'resource' in aitem.tag: elif 'resource' in aitem.tag:
self.clientresource = aitem.text self.clientresource = aitem.text
resource = self.clientresource
if bumper.check_authcode(self.uid, password): if not self.uid.startswith("fuid"):
bumper_bots = self.bumper_bots.get()
bumper_clients = self.bumper_clients.get()
for bot in bumper_bots:
if self.uid == bot.did:
bot.xmpp_connection = True
xmppserverlog.info("bot connected {}".format(bot.did))
self.bumper_config['bumper_bots'].set(bumper_bots) #Need sample data to see details here
bumper.add_bot('',self.uid, '', resource)
for client in bumper_clients: xmppserverlog.info("bot authenticated {}".format(self.uid))
if self.uid == client.userid and client.userid != 'helper1':
client.xmpp_connection = True
xmppserverlog.info("client connected {}".format(client.userid))
self.bumper_config['bumper_clients'].set(bumper_clients)
#Client authenticated, move to next state #Client authenticated, move to next state
self._set_state('INIT') self._set_state('INIT')
@ -384,8 +375,26 @@ class Client(threading.Thread):
self.send('<iq type="result" id="{}"/>'.format(xml.get('id'))) self.send('<iq type="result" id="{}"/>'.format(xml.get('id')))
else: else:
#Failed auth auth = False
self.send('<iq type="error" id="{}"><error code="401" type="auth"><not-authorized xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"/></error></iq>'.format(xml.get('id'))) if bumper.check_authcode(self.uid, authcode):
auth = True
elif bumper.use_auth == False:
auth = True
if auth:
bumper.add_client(self.uid, 'bumper', self.clientresource)
xmppserverlog.debug("client authenticated {}".format(self.uid))
#Client authenticated, move to next state
self._set_state('INIT')
#Successful auth
self.send('<iq type="result" id="{}"/>'.format(xml.get('id')))
else:
#Failed auth
self.send('<iq type="error" id="{}"><error code="401" type="auth"><not-authorized xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"/></error></iq>'.format(xml.get('id')))
except ET.ParseError as e: except ET.ParseError as e:
if "no element found" in e.msg: if "no element found" in e.msg:
@ -409,8 +418,10 @@ class Client(threading.Thread):
self.clientresource = resource self.clientresource = resource
authcode = saslauth[2] authcode = saslauth[2]
if bumper.check_authcode(self.uid, authcode): if not self.uid.startswith("fuid"):
#Need sample data to see details here
bumper.add_bot('',self.uid, '', resource)
xmppserverlog.info("bot authenticated {}".format(self.uid))
#Send response #Send response
self.send('<success xmlns="urn:ietf:params:xml:ns:xmpp-sasl"/>') #Success self.send('<success xmlns="urn:ietf:params:xml:ns:xmpp-sasl"/>') #Success
@ -418,8 +429,25 @@ class Client(threading.Thread):
self._set_state('INIT') self._set_state('INIT')
else: else:
#Failed to authenticate auth = False
self.send('<response xmlns="urn:ietf:params:xml:ns:xmpp-sasl"/>') #Fail if bumper.check_authcode(self.uid, authcode):
auth = True
elif bumper.use_auth == False:
auth = True
if auth:
bumper.add_client(self.uid, 'bumper', self.clientresource)
xmppserverlog.debug("client authenticated {}".format(self.uid))
#Client authenticated, move to next state
self._set_state('INIT')
#Send response
self.send('<success xmlns="urn:ietf:params:xml:ns:xmpp-sasl"/>') #Success
else:
#Failed to authenticate
self.send('<response xmlns="urn:ietf:params:xml:ns:xmpp-sasl"/>') #Fail
except ET.ParseError as e: except ET.ParseError as e:
if "no element found" in e.msg: if "no element found" in e.msg:
@ -434,6 +462,21 @@ class Client(threading.Thread):
def _handle_bind(self, xml): def _handle_bind(self, xml):
try: try:
bumper_bots = self.bumper_bots.get()
bumper_clients = self.bumper_clients.get()
for bot in bumper_bots:
if self.uid == bot.did:
bot.xmpp_connection = True
#xmppserverlog.info("bot connected {}".format(bot.did))
self.bumper_bots.set(bumper_bots)
for client in bumper_clients:
if self.uid == client.userid:
client.xmpp_connection = True
#xmppserverlog.info("client connected {}".format(client.userid))
self.bumper_clients.set(bumper_clients)
clientbindxml = xml.getchildren() clientbindxml = xml.getchildren()
clientresourcexml = clientbindxml[0].getchildren() clientresourcexml = clientbindxml[0].getchildren()
if len(clientresourcexml) > 0: if len(clientresourcexml) > 0:
@ -546,7 +589,7 @@ class Client(threading.Thread):
def run(self): def run(self):
xmppserverlog.info('client connected - {}'.format(self.address)) #xmppserverlog.info('client connected - {}'.format(self.address))
self._set_state('CONNECT') self._set_state('CONNECT')
while not self.state == self.DISCONNECT and not self.connection._closed: while not self.state == self.DISCONNECT and not self.connection._closed: