add basic auth

Add a very basic authentication and user system
This commit is contained in:
Brian Martin 2019-02-16 23:37:54 -05:00
parent acbcb8707a
commit 54325fbe29
5 changed files with 189 additions and 99 deletions

View file

@ -30,11 +30,20 @@ def main():
xmpp_address = (listen_host, 5223)
mqtt_address = (listen_host, 8883)
xmpp_server = bumper.XMPPServer(xmpp_address)
mqtt_server = bumper.MQTTServer(mqtt_address,bumper_bots=bumper.bumper_bots_var,bumper_clients=bumper.bumper_clients_var,remove_clients=bumper.bumper_removeclients_var)
xmpp_server = bumper.XMPPServer(xmpp_address, bumper_users=bumper.bumper_users_var, bumper_bots=bumper.bumper_bots_var,bumper_clients=bumper.bumper_clients_var)
mqtt_server = bumper.MQTTServer(mqtt_address, bumper_bots=bumper.bumper_bots_var,bumper_clients=bumper.bumper_clients_var)
mqtt_helperbot = bumper.MQTTHelperBot(mqtt_address, bumper_bots=bumper.bumper_bots_var,bumper_clients=bumper.bumper_clients_var)
conf_server = bumper.ConfServer(conf_address_443, usessl=True, bumper_bots=bumper.bumper_bots_var,bumper_clients=bumper.bumper_clients_var, remove_clients=bumper.bumper_removeclients_var,helperbot=mqtt_helperbot)
conf_server_2 = bumper.ConfServer(conf_address_8007, usessl=False, bumper_bots=bumper.bumper_bots_var,bumper_clients=bumper.bumper_clients_var, helperbot=mqtt_helperbot,remove_clients=bumper.bumper_removeclients_var)
conf_server = bumper.ConfServer(conf_address_443, usessl=True,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
users = bumper.bumper_users_var.get()
user1 = bumper.BumperUser('user1')
user1.add_device('user_phone')
user1.add_bot('bot_id')
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
@ -58,17 +67,19 @@ def main():
time.sleep(0.25)
# WIP: Remove clients that have disconnected
remove_clients = bumper.bumper_removeclients_var.get()
if len(remove_clients) > 0:
for uid in remove_clients:
if uid != "":
xmpp_server.remove_client_byuid(uid) #Remove clients from xmpp server
remove_clients.remove(uid)
# remove_clients = bumper.bumper_removeclients_var.get()
# if len(remove_clients) > 0:
# for uid in remove_clients:
# if uid != "":
# xmpp_server.remove_client_byuid(uid) #Remove clients from xmpp server
# remove_clients.remove(uid)
bumper.bumper_removeclients_var.set(remove_clients)
# bumper.bumper_removeclients_var.set(remove_clients)
except KeyboardInterrupt:
bumper.bumperlog.info("Bumper Exiting - Keyboard Interrupt")
print("Bumper Exiting")
exit(1)
if __name__ == "__main__":
main()

View file

@ -8,10 +8,12 @@ import asyncio
import contextvars
import time
import logging
from base64 import b64decode, b64encode
bumper_users_var = contextvars.ContextVar('bumper_users', default=[])
bumper_clients_var = contextvars.ContextVar('bumper_clients', default=[])
bumper_bots_var = contextvars.ContextVar('bumper_bots', default=[])
bumper_removeclients_var = contextvars.ContextVar('bumper_removeclients', default=[])
ca_cert = './certs/CA/cacert.pem'
server_cert = './certs/cert.pem'
server_key = './certs/key.pem'
@ -35,6 +37,47 @@ def get_milli_time(timetoconvert):
return int(round(timetoconvert * 1000))
class BumperUser(object):
def __init__(self,userid=""):
self.userid = userid
self.devices = []
self.tokens = []
self.authcodes = []
self.bots = []
def add_device(self, devid):
if not devid in self.devices:
self.devices.append(devid)
def remove_device(self, devid):
if devid in self.devices:
self.devices.remove(devid)
def add_token(self, token):
if not token in self.tokens:
self.tokens.append(token)
def revoke_token(self, token):
if token in self.tokens:
self.tokens.remove(token)
def add_authcode(self, authcode):
if not authcode in self.authcodes:
self.authcodes.append(authcode)
def revoke_authcode(self, authcode):
if authcode in self.authcodes:
self.authcodes.remove(authcode)
def add_bot(self, botdid):
if not botdid in self.bots:
self.bots.append(botdid)
def remove_bot(self, botdid):
if botdid in self.bots:
self.bots.remove(botdid)
class VacBotDevice(object):
def __init__(self,did="", vac_bot_device_class="",resource="" , name="", nick="", company="eco-ng"):
self.vac_bot_device_class = vac_bot_device_class
@ -48,12 +91,11 @@ class VacBotDevice(object):
return {"class": self.vac_bot_device_class, "company": self.company,
"did": self.did, "name": self.name, "nick": self.nick, "resource": self.resource}
class VacBotUser(object):
class VacBotClient(object):
def __init__(self,userid="",realm="",token=""):
self.userid = userid
self.realm = realm
self.resource = token
def asdict(self):
return {"userid": self.userid,"realm": self.realm,"resource": self.resource}

View file

@ -10,6 +10,7 @@ from datetime import datetime, timedelta
import asyncio
import contextvars
from aiohttp import web
import uuid
class aiohttp_filter(logging.Filter):
@ -32,10 +33,10 @@ class ConfServer():
bumper_clients = contextvars.ContextVar
bumper_bots = contextvars.ContextVar
def __init__(self, address, usessl=False, bumper_bots=contextvars.ContextVar, bumper_clients=contextvars.ContextVar, remove_clients=contextvars.ContextVar,helperbot=None):
def __init__(self, address, usessl=False, bumper_users=contextvars.ContextVar, bumper_bots=contextvars.ContextVar, bumper_clients=contextvars.ContextVar, helperbot=None):
self.bumper_users = bumper_users
self.bumper_bots = bumper_bots
self.bumper_clients = bumper_clients
self.remove_clients = remove_clients
self.helperbot = helperbot
self.usessl = usessl
self.address = address
@ -82,7 +83,7 @@ class ConfServer():
app.add_routes([
web.get('', self.handle_base),
web.get('/{apiversion}/private/{country}/{language}/{devid}/{apptype}/{appversion}/{devtype}/{aid}/user/login', self.handle_login),
# web.get('/{apiversion}/private/{country}/{language}/{devid}/{apptype}/{appversion}/{devtype}/{aid}/user/checkLogin', self.handle_checkLogin),
web.get('/{apiversion}/private/{country}/{language}/{devid}/{apptype}/{appversion}/{devtype}/{aid}/user/checkLogin', self.handle_login),
web.get('/{apiversion}/private/{country}/{language}/{devid}/{apptype}/{appversion}/{devtype}/{aid}/user/logout', self.handle_logout),
web.get('/{apiversion}/private/{country}/{language}/{devid}/{apptype}/{appversion}/{devtype}/{aid}/user/getAuthCode', self.handle_getAuthCode),
web.get('/{apiversion}/private/{country}/{language}/{devid}/{apptype}/{appversion}/{devtype}/{aid}/user/checkAgreement', self.handle_checkAgreement),
@ -96,6 +97,9 @@ class ConfServer():
web.post('/lookup.do', self.handle_lookup),
])
#Direct register from app:
#/{apiversion}/private/{country}/{language}/{devid}/{apptype}/{appversion}/{devtype}/{aid}/user/directRegister
runner = web.AppRunner(app)
@ -131,60 +135,64 @@ class ConfServer():
confserverlog.exception('{}'.format(e))
async def handle_login(self, request):
try:
#Could implement basic auth if you wanted, or just accept anything
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 = bumper.bumper_users_var.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']
else:
if tmpaccesstoken == '':
tmpaccesstoken = uuid.uuid4().hex
user.add_token(tmpaccesstoken)
body = {
"code": "0000",
"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())
}
bumper.bumper_users_var.set(users)
return web.json_response(body)
body = {
"code": "0000",
"data": {
"accessToken": "tempaccesstoken", #Random chars 32 length
"country": countrycode,
"email": "null@null.com",
"uid": "fuid_{}".format(''.join(random.sample(string.ascii_letters,6))), #Date(14)_RandomChars(32)
"username": "fusername_{}".format(''.join(random.sample(string.ascii_letters,6))) #Random chars 8
},
"msg": "操作成功",
"code": "1005",
"data": None,
"msg": "当前密码错误",
"time": bumper.get_milli_time(time.time())
}
return web.json_response(body)
except Exception as e:
confserverlog.exception('{}'.format(e))
async def handle_checkLogin(self, request):
try:
# The app seems to remember it's last uid and accessToken
# If these don't match, it fails
countrycode = request.match_info.get('country', "us")
body = {
"code": "0000",
"data": {
"accessToken": "tempaccesstoken", #Random chars 32 length
"country": countrycode,
"email": "null@null.com",
"uid": "fuid_{}".format(''.join(random.sample(string.ascii_letters,6))), #Date(14)_RandomChars(32)
"username": "fusername_{}".format(''.join(random.sample(string.ascii_letters,6))) #Random chars 8
},
"msg": "操作成功",
"time": bumper.get_milli_time(time.time())
}
return web.json_response(body)
except Exception as e:
confserverlog.exception('{}'.format(e))
async def handle_logout(self, request):
try:
uid = request.query['uid']
user_devid = request.match_info.get('devid', "")
if not user_devid == "":
users = bumper.bumper_users_var.get()
for user in users:
if user_devid in user.devices:
if request.query['uid'] == "fuid_{}".format(user.userid) and request.query['accessToken'] in user.tokens:
user.revoke_token(request.query['accessToken'])
bumper.bumper_users_var.set(users)
body = {"code": "0000","data": None,"msg": "操作成功", "time": bumper.get_milli_time(time.time())}
# QUERY String
# 'uid=fuid_CUOVIn&accessToken=tempaccesstoken&requestId=e584de79f9cca854df6fb3352c6893b6&authTimespan=1550168440575&authTimeZone=GMT-5&authAppkey=eJUWrzRv34qFSaYk&authSign=14e38f95c7316e111c5815cf15f0f972'
if not uid == "":
remove_clients = self.remove_clients.get()
remove_clients.append(request.query['uid'])
self.remove_clients.set(remove_clients)
return web.json_response(body)
@ -193,14 +201,32 @@ class ConfServer():
async def handle_getAuthCode(self, request):
try:
countrycode = request.match_info.get('country', "us")
user_devid = request.match_info.get('devid', "")
if not user_devid == "":
users = bumper.bumper_users_var.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": "0000",
"data": {
"authCode": tmpauthcode,
"ecovacsUid": request.query['uid']
},
"msg": "操作成功",
"time": bumper.get_milli_time(time.time())
}
bumper.bumper_users_var.set(users)
return web.json_response(body)
body = {
"code": "0000",
"data": {
"authCode": "{}_tempauthcode".format(countrycode), #countrycode_randomchars(32)
"ecovacsUid": "fuid_{}".format(''.join(random.sample(string.ascii_letters,6))) #Date(14)_RandomChars(32)
},
"msg": "操作成功",
"code": "1005",
"data": None,
"msg": "当前密码错误",
"time": bumper.get_milli_time(time.time())
}
@ -271,7 +297,6 @@ class ConfServer():
async def handle_getProductIotMap(self, request):
try:
#json_body = json.loads(await request.text())
body = {"code":0,"data":[{"classid":"dl8fht","product":{"_id":"5acb0fa87c295c0001876ecf","name":"DEEBOT 600 Series","icon":"5acc32067c295c0001876eea","UILogicId":"dl8fht","ota":False,"iconUrl":"https://portal-ww.ecouser.net/api/pim/file/get/5acc32067c295c0001876eea"}},{"classid":"02uwxm","product":{"_id":"5ae1481e7ccd1a0001e1f69e","name":"DEEBOT OZMO Slim10 Series","icon":"5b1dddc48bc45700014035a1","UILogicId":"02uwxm","ota":False,"iconUrl":"https://portal-ww.ecouser.net/api/pim/file/get/5b1dddc48bc45700014035a1"}},{"classid":"y79a7u","product":{"_id":"5b04c0227ccd1a0001e1f6a8","name":"DEEBOT OZMO 900","icon":"5b04c0217ccd1a0001e1f6a7","UILogicId":"y79a7u","ota":True,"iconUrl":"https://portal-ww.ecouser.net/api/pim/file/get/5b04c0217ccd1a0001e1f6a7"}},{"classid":"jr3pqa","product":{"_id":"5b43077b8bc457000140363e","name":"DEEBOT 711","icon":"5b5ac4cc8d5a56000111e769","UILogicId":"jr3pqa","ota":True,"iconUrl":"https://portal-ww.ecouser.net/api/pim/file/get/5b5ac4cc8d5a56000111e769"}},{"classid":"uv242z","product":{"_id":"5b5149b4ac0b87000148c128","name":"DEEBOT 710","icon":"5b5ac4e45f21100001882bb9","UILogicId":"uv242z","ota":True,"iconUrl":"https://portal-ww.ecouser.net/api/pim/file/get/5b5ac4e45f21100001882bb9"}},{"classid":"ls1ok3","product":{"_id":"5b6561060506b100015c8868","name":"DEEBOT 900 Series","icon":"5ba4a2cb6c2f120001c32839","UILogicId":"ls1ok3","ota":True,"iconUrl":"https://portal-ww.ecouser.net/api/pim/file/get/5ba4a2cb6c2f120001c32839"}}]}
return web.json_response(body)
@ -297,13 +322,18 @@ class ConfServer():
elif service == 'EcoUpdate':
body = {"result":"ok","ip":"47.88.66.164","port":8005}
elif todo == 'loginByItToken':
body = {
"resource": postbody["resource"],
"result": "ok",
"todo": "result",
"token": postbody["token"], #RandomChar(32)
"userId": postbody["userId"] #RandomChar(16)
}
users = bumper.bumper_users_var.get()
for user in users:
if postbody['userId'] == "fuid_{}".format(user.userid) and postbody['token'] in user.authcodes:
body = {
"resource": postbody["resource"],
"result": "ok",
"todo": "result",
"token": postbody["token"],
"userId": postbody["userId"]
}
elif todo == 'GetDeviceList':
active_bots = self.bumper_bots.get()
body = {

View file

@ -174,12 +174,11 @@ class MQTTServer():
except Exception as e:
mqttserverlog.exception('{}'.format(e))
def __init__(self, address, bumper_bots=contextvars.ContextVar, bumper_clients=contextvars.ContextVar,remove_clients=contextvars.ContextVar):
def __init__(self, address, bumper_bots=contextvars.ContextVar, bumper_clients=contextvars.ContextVar):
try:
self.bumper_bots = bumper_bots
self.bumper_clients = bumper_clients
self.remove_clients = remove_clients
self.mqttserverthread = None
self.address = address
@ -216,7 +215,6 @@ class MQTTServer():
'clients':{
'connected_bots': self.bumper_bots,
'connected_clients': self.bumper_clients,
'remove_clients': self.remove_clients
},
}
@ -285,7 +283,7 @@ class BumperMQTTServer_Plugin:
self.clients['connected_bots'].set(connected_bots)
else:
tmpuserdetail = str(didsplit[1]).split("/")
newuser = bumper.VacBotUser()
newuser = bumper.VacBotClient()
newuser.userid = didsplit[0]
newuser.realm = tmpuserdetail[0]
newuser.resource = tmpuserdetail[1]
@ -314,7 +312,7 @@ class BumperMQTTServer_Plugin:
#mqttserverlog.debug('%s disconnected' % client_id)
connected_bots = self.clients['connected_bots'].get()
connected_clients = self.clients['connected_clients'].get()
remove_clients = self.clients['remove_clients'].get()
#remove_clients = self.clients['remove_clients'].get()
didsplit = str(client_id).split("@")
#If the did is in the list, remove it
for bot in connected_bots:

View file

@ -4,6 +4,7 @@ from threading import Thread
import sys, socket, threading, re, time, logging, uuid, xml.etree.ElementTree as ET
import base64
import ssl
import contextvars
import bumper
xmppserverlog = logging.getLogger("xmppserver")
@ -15,9 +16,12 @@ class XMPPServer():
clients = []
exit_flag = False
def __init__(self, address):
def __init__(self, address, bumper_users=contextvars.ContextVar, bumper_bots=contextvars.ContextVar, bumper_clients=contextvars.ContextVar):
# Initialize bot server
self.address = address
self.bumper_users = bumper_users
self.bumper_bots = bumper_bots
self.bumper_clients = bumper_clients
def run(self, run_async=False):
@ -88,8 +92,6 @@ class XMPPServer():
self.socket.close()
def disconnect(self):
try:
xmppserverlog.debug('waiting for all client threads to exit')
@ -340,7 +342,7 @@ class Client(threading.Thread):
elif 'resource' in aitem.tag:
self.clientresource = aitem.text
if True:
if self.check_authcode(self.uid, password):
#Client authenticated, move to next state
self._set_state('INIT')
@ -364,6 +366,13 @@ class Client(threading.Thread):
except Exception as e:
xmppserverlog.exception('{}'.format(e))
def check_authcode(self, uid, authcode):
users = bumper.bumper_users_var.get()
for user in users:
if uid == "fuid_{}".format(user.userid) and authcode in user.authcodes:
return True
return False
def _handle_sasl_auth(self, data):
try:
@ -376,7 +385,7 @@ class Client(threading.Thread):
self.clientresource = resource
authcode = saslauth[2]
if authcode == "us_tempauthcode": #Handle auth
if self.check_authcode(self.uid, authcode):
#Send response
self.send('<success xmlns="urn:ietf:params:xml:ns:xmpp-sasl"/>') #Success