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
***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")
@ -105,12 +107,51 @@ class VacBotClient(object):
return {"userid": self.userid,"realm": self.realm,"resource": self.resource}
def check_authcode(uid, authcode):
users = bumper_users_var.get()
for user in users:
if uid == "fuid_{}".format(user.userid) and authcode in user.authcodes:
return True
users = bumper_users_var.get()
for user in users:
if uid == "fuid_{}".format(user.userid) and authcode in user.authcodes:
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"
ERR_ACTIVATE_TOKEN_TIMEOUT = "1006"

View file

@ -134,73 +134,134 @@ class ConfServer():
except Exception as e:
confserverlog.exception('{}'.format(e))
async def handle_login(self, request):
async def handle_login(self, request):
try:
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 = self.bumper_users.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']
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": "操作成功",
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:
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']
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())
}
else:
body = {
"code": bumper.ERR_TOKEN_INVALID,
"data": None,
"msg": "当前密码错误",
"time": bumper.get_milli_time(time.time())
}
}
else:
if tmpaccesstoken == '':
tmpaccesstoken = uuid.uuid4().hex
user.add_token(tmpaccesstoken)
body = {
"code": bumper.ERR_TOKEN_INVALID,
"data": None,
"msg": "当前密码错误",
"time": bumper.get_milli_time(time.time())
}
else:
if tmpaccesstoken == '':
tmpaccesstoken = uuid.uuid4().hex
user.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(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,
"data": None,
"msg": "当前密码错误",
"time": bumper.get_milli_time(time.time())
}
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,
"data": None,
"msg": "当前密码错误",
"time": bumper.get_milli_time(time.time())
}
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,30 +286,31 @@ class ConfServer():
user_devid = request.match_info.get('devid', "")
if not user_devid == "":
users = self.bumper_users.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": bumper.RETURN_API_SUCCESS,
"data": {
"authCode": tmpauthcode,
"ecovacsUid": request.query['uid']
},
"msg": "操作成功",
"time": bumper.get_milli_time(time.time())
}
self.bumper_users.set(users)
return web.json_response(body)
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")
tmpauthcode = "{}_{}".format(countrycode,uuid.uuid4().hex)
user.add_authcode(tmpauthcode)
body = {
"code": bumper.RETURN_API_SUCCESS,
"data": {
"authCode": tmpauthcode,
"ecovacsUid": request.query['uid']
},
"msg": "操作成功",
"time": bumper.get_milli_time(time.time())
}
self.bumper_users.set(users)
return web.json_response(body)
body = {
"code": bumper.ERR_TOKEN_INVALID,
"data": None,
"msg": "当前密码错误",
"time": bumper.get_milli_time(time.time())
}
}
return web.json_response(body)
@ -412,12 +474,18 @@ class ConfServer():
async def handle_devmanager_botcommand(self, request):
try:
json_body = json.loads(await request.text())
randomid = ''.join(random.sample(string.ascii_letters,6))
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)
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

@ -284,51 +284,36 @@ class BumperMQTTServer_Plugin:
didsplit = str(client_id).split("@")
#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)
tmpbotdetail = str(didsplit[1]).split("/")
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
tmpclientdetail = str(didsplit[1]).split("/")
userid = didsplit[0]
realm = tmpclientdetail[0]
resource = tmpclientdetail[1]
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)
if userid == "helper1":
authenticated = True
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:
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,37 +356,46 @@ 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
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))
self.bumper_config['bumper_bots'].set(bumper_bots)
resource = self.clientresource
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))
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))
self.bumper_config['bumper_clients'].set(bumper_clients)
#Client authenticated, move to next state
self._set_state('INIT')
#Successful auth
self.send('<iq type="result" id="{}"/>'.format(xml.get('id')))
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
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')))
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:
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,17 +418,36 @@ 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')
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))
else:
#Failed to authenticate
self.send('<response xmlns="urn:ietf:params:xml:ns:xmpp-sasl"/>') #Fail
#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:
if "no element found" in e.msg:
@ -433,7 +461,22 @@ class Client(threading.Thread):
xmppserverlog.exception('{}'.format(e))
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()
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: