update auth mechanisms

use_auth allows the newer auth mechanisms to be optional
changes and fixes to support
TODO: need to get a bot that would connect on XMPP or get sniffed traffic to see the login details
This commit is contained in:
Brian Martin 2019-02-20 02:53:49 -05:00
parent d316862120
commit 0a5d93b099
6 changed files with 306 additions and 166 deletions

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")
@ -112,6 +114,45 @@ def check_authcode(uid, authcode):
return False 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_API_SUCCESS = "0000" RETURN_API_SUCCESS = "0000"
ERR_ACTIVATE_TOKEN_TIMEOUT = "1006" ERR_ACTIVATE_TOKEN_TIMEOUT = "1006"
ERR_COMMON = "0001" ERR_COMMON = "0001"

View file

@ -135,10 +135,11 @@ 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")
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 if not user_devid == "": #Performing basic "auth" using devid, super insecure
users = self.bumper_users.get() users = self.bumper_users.get()
for user in users: for user in users:
@ -195,12 +196,72 @@ class ConfServer():
} }
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,6 +286,7 @@ 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()
if len(users) > 0:
for user in users: for user in users:
if user_devid in user.devices and request.query['accessToken'] in user.tokens: if user_devid in user.devices and request.query['accessToken'] in user.tokens:
countrycode = request.match_info.get('country', "us") countrycode = request.match_info.get('country', "us")
@ -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))
bots = self.bumper_bots.get()
for bot in bots:
if bot.did == json_body['toId'] and bot.mqtt_connection == True:
retcmd = await self.helperbot.send_command(json_body, randomid) retcmd = await self.helperbot.send_command(json_body, randomid)
body = retcmd body = retcmd
confserverlog.debug("\r\n POST: {} \r\n Response: {}".format(json_body,body)) confserverlog.debug("\r\n POST: {} \r\n Response: {}".format(json_body,body))
return web.json_response(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":
authenticated = True
elif bumper.check_authcode(didsplit[0], password):
tmpclientdetail = str(didsplit[1]).split("/") tmpclientdetail = str(didsplit[1]).split("/")
newclient = bumper.VacBotClient() userid = didsplit[0]
newclient.userid = didsplit[0] realm = tmpclientdetail[0]
newclient.realm = tmpclientdetail[0] resource = tmpclientdetail[1]
newclient.resource = tmpclientdetail[1]
existingclient = False
for client in bumper_clients:
if client.userid == newclient.userid:
existingclient = True
if existingclient == False: if userid == "helper1":
bumper_clients.append(newclient) authenticated = True
mqttserverlog.info("new client authenticated {}".format(newclient.userid)) else:
self.bumper_config['bumper_clients'].set(bumper_clients) 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 authenticated = True
else: else:
authenticated = False 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,34 @@ 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)
xmppserverlog.info("bot authenticated {}".format(self.uid))
for client in bumper_clients: #Client authenticated, move to next state
if self.uid == client.userid and client.userid != 'helper1': self._set_state('INIT')
client.xmpp_connection = True
xmppserverlog.info("client connected {}".format(client.userid))
self.bumper_config['bumper_clients'].set(bumper_clients) #Successful auth
self.send('<iq type="result" id="{}"/>'.format(xml.get('id')))
else:
auth = False
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 #Client authenticated, move to next state
self._set_state('INIT') self._set_state('INIT')
@ -387,6 +395,7 @@ class Client(threading.Thread):
#Failed auth #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'))) 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:
xmppserverlog.debug('xml parse error - {} - {} - this is common with ecovac protocol'.format(data.decode('utf-8'), e)) xmppserverlog.debug('xml parse error - {} - {} - this is common with ecovac protocol'.format(data.decode('utf-8'), e))
@ -409,14 +418,33 @@ 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
#Client authenticated, move to next state #Client authenticated, move to next state
self._set_state('INIT') self._set_state('INIT')
else:
auth = False
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: else:
#Failed to authenticate #Failed to authenticate
self.send('<response xmlns="urn:ietf:params:xml:ns:xmpp-sasl"/>') #Fail self.send('<response xmlns="urn:ietf:params:xml:ns:xmpp-sasl"/>') #Fail
@ -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: