diff --git a/README.md b/README.md
index 15c68b3..dbb8492 100644
--- a/README.md
+++ b/README.md
@@ -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
diff --git a/bumper.py b/bumper.py
index 0f86cbc..be48215 100644
--- a/bumper.py
+++ b/bumper.py
@@ -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
diff --git a/bumper/__init__.py b/bumper/__init__.py
index 45691ae..6ae059c 100644
--- a/bumper/__init__.py
+++ b/bumper/__init__.py
@@ -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"
diff --git a/bumper/confserver.py b/bumper/confserver.py
index 87726cb..8f8d5db 100644
--- a/bumper/confserver.py
+++ b/bumper/confserver.py
@@ -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))
diff --git a/bumper/mqttserver.py b/bumper/mqttserver.py
index d3246bf..91e733c 100644
--- a/bumper/mqttserver.py
+++ b/bumper/mqttserver.py
@@ -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:
diff --git a/bumper/xmppserver.py b/bumper/xmppserver.py
index 552da52..a71b7c9 100644
--- a/bumper/xmppserver.py
+++ b/bumper/xmppserver.py
@@ -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(''.format(xml.get('id')))
+ self.send(''.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(''.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(''.format(xml.get('id')))
+
+ else:
+ #Failed auth
+ self.send(''.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
#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('') #Fail
+ #Client authenticated, move to next state
+ self._set_state('INIT')
+
+ #Send response
+ self.send('') #Success
+
+ else:
+ #Failed to authenticate
+ self.send('') #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: