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
***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

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)
#add user
users = bumper.bumper_users_var.get()
user1 = bumper.BumperUser('user1')
user1.add_device('devid')
user1.add_bot('bot_did')
users.append(user1)
bumper.bumper_users_var.set(users)
# users = bumper.bumper_users_var.get()
# user1 = bumper.BumperUser('user1')
# user1.add_device('devid')
# user1.add_bot('bot_did')
# 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

View file

@ -18,6 +18,8 @@ ca_cert = './certs/CA/cacert.pem'
server_cert = './certs/cert.pem'
server_key = './certs/key.pem'
use_auth = False
#Logs
bumperlog = logging.getLogger("bumper")
confserverlog = logging.getLogger("confserver")
@ -112,6 +114,45 @@ def check_authcode(uid, authcode):
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"
ERR_ACTIVATE_TOKEN_TIMEOUT = "1006"
ERR_COMMON = "0001"

View file

@ -135,10 +135,11 @@ class ConfServer():
confserverlog.exception('{}'.format(e))
async def handle_login(self, request):
try:
user_devid = request.match_info.get('devid', "")
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
users = self.bumper_users.get()
for user in users:
@ -195,12 +196,72 @@ class ConfServer():
}
return web.json_response(body)
else:
return web.json_response(self._auth_any(user_devid, countrycode, request))
except Exception as 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):
try:
user_devid = request.match_info.get('devid', "")
@ -225,6 +286,7 @@ class ConfServer():
user_devid = request.match_info.get('devid', "")
if not user_devid == "":
users = self.bumper_users.get()
if len(users) > 0:
for user in users:
if user_devid in user.devices and request.query['accessToken'] in user.tokens:
countrycode = request.match_info.get('country', "us")
@ -413,11 +475,17 @@ class ConfServer():
try:
json_body = json.loads(await request.text())
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)
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:
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 not (str(didsplit[0]).startswith("fuid") or str(didsplit[0]).startswith("helper")):
tmpbotdetail = str(didsplit[1]).split("/")
newbot = bumper.VacBotDevice()
newbot.did = 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)
bumper.add_bot(username, didsplit[0], tmpbotdetail[0], tmpbotdetail[1])
mqttserverlog.debug("new bot authenticated SN: {} DID: {}".format(username, didsplit[0]))
authenticated = True
else:
if didsplit[0] == "helper1":
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
userid = didsplit[0]
realm = tmpclientdetail[0]
resource = tmpclientdetail[1]
if existingclient == False:
bumper_clients.append(newclient)
mqttserverlog.info("new client authenticated {}".format(newclient.userid))
self.bumper_config['bumper_clients'].set(bumper_clients)
if userid == "helper1":
authenticated = True
else:
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:
self.context.logger.warning("Session informations not available")
self.context.logger.warning("Session information not available")
authenticated = False
return authenticated
@ -343,13 +328,13 @@ class BumperMQTTServer_Plugin:
for bot in bumper_bots:
if didsplit[0] == bot.did:
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)
for client in bumper_clients:
if didsplit[0] == client.userid and client.userid != 'helper1':
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)
except Exception as e:
@ -366,13 +351,13 @@ class BumperMQTTServer_Plugin:
for bot in bumper_bots:
if didsplit[0] == bot.did:
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)
for client in bumper_clients:
if didsplit[0] == client.userid and client.userid != 'helper1':
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)
except Exception as e:

View file

@ -185,14 +185,14 @@ class Client(threading.Thread):
for bot in bumper_bots:
if self.uid == bot.did:
bot.xmpp_connection = False
xmppserverlog.info("bot disconnected {}".format(bot.did))
#xmppserverlog.info("bot disconnected {}".format(bot.did))
self.bumper_bots.set(bumper_bots)
for client in bumper_clients:
if self.uid == client.userid and client.userid != 'helper1':
client.xmpp_connection = False
xmppserverlog.info("client disconnected {}".format(client.userid))
#xmppserverlog.info("client disconnected {}".format(client.userid))
self.bumper_clients.set(bumper_clients)
#xmppserverlog.debug('client {} with resource {} disconnecting'.format(self.address, self.clientresource))
@ -356,26 +356,34 @@ class Client(threading.Thread):
elif 'password' in aitem.tag:
password = aitem.text.split("/")[2]
authcode = password
elif 'resource' in aitem.tag:
self.clientresource = aitem.text
resource = self.clientresource
if bumper.check_authcode(self.uid, password):
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))
if not self.uid.startswith("fuid"):
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:
if self.uid == client.userid and client.userid != 'helper1':
client.xmpp_connection = True
xmppserverlog.info("client connected {}".format(client.userid))
#Client authenticated, move to next state
self._set_state('INIT')
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
self._set_state('INIT')
@ -387,6 +395,7 @@ class Client(threading.Thread):
#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:
if "no element found" in e.msg:
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
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
self.send('<success xmlns="urn:ietf:params:xml:ns:xmpp-sasl"/>') #Success
#Client authenticated, move to next state
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:
#Failed to authenticate
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):
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()
clientresourcexml = clientbindxml[0].getchildren()
if len(clientresourcexml) > 0:
@ -546,7 +589,7 @@ class Client(threading.Thread):
def run(self):
xmppserverlog.info('client connected - {}'.format(self.address))
#xmppserverlog.info('client connected - {}'.format(self.address))
self._set_state('CONNECT')
while not self.state == self.DISCONNECT and not self.connection._closed: