From 0b9f192ded294a82d8ad6e2eb9fa2274eeaf3067 Mon Sep 17 00:00:00 2001 From: Robert Resch Date: Sun, 11 Apr 2021 16:26:15 +0200 Subject: [PATCH] add oauth --- bumper/__init__.py | 1 + bumper/confserver.py | 25 ++++++++++-- bumper/db.py | 37 +++++++++++++++++- bumper/models.py | 32 +++++++++++++++ .../bumper_confserver_portal_appsvr.py | 19 ++++++++- .../plugins/bumper_confserver_portal_rapp.py | 39 +++++++++++++++++++ tests/test_models.py | 14 +++++++ 7 files changed, 161 insertions(+), 6 deletions(-) create mode 100644 bumper/plugins/bumper_confserver_portal_rapp.py create mode 100644 tests/test_models.py diff --git a/bumper/__init__.py b/bumper/__init__.py index ccce858..950e1cb 100644 --- a/bumper/__init__.py +++ b/bumper/__init__.py @@ -57,6 +57,7 @@ bumper_announce_ip = os.environ.get("BUMPER_ANNOUNCE_IP") or bumper_listen bumper_debug = strtobool(os.environ.get("BUMPER_DEBUG")) or False use_auth = False token_validity_seconds = 3600 # 1 hour +oauth_validity_days = 15 db = None mqtt_server = None diff --git a/bumper/confserver.py b/bumper/confserver.py index 8b9cec2..af8679e 100644 --- a/bumper/confserver.py +++ b/bumper/confserver.py @@ -62,13 +62,12 @@ class ConfServer: self.app.add_routes( [ - - web.get("", self.handle_base, name="base"), + web.get("", self.handle_base, name="base"), web.get("/bot/remove/{did}", self.handle_RemoveBot, name='remove-bot'), web.get("/client/remove/{resource}", self.handle_RemoveClient, name='remove-client'), web.get("/restart_{service}", self.handle_RestartService, name='restart-service'), web.post("/lookup.do", self.handle_lookup), - + web.post("/newauth.do", self.handle_newauth), ] ) @@ -507,6 +506,26 @@ class ConfServer: except Exception as e: confserverlog.exception("{}".format(e)) + async def handle_newauth(self, request): + # Bumper is only returning the submitted token. No reason yet to create another new token + try: + if request.content_type == "application/x-www-form-urlencoded": + postbody = await request.post() + else: + postbody = json.loads(await request.text()) + + confserverlog.debug(postbody) + + body = { + "authCode": postbody["itToken"], + "result": "ok", + "todo": "result" + } + + return web.json_response(body) + + except Exception as e: + confserverlog.exception("{}".format(e)) async def disconnect(self): try: diff --git a/bumper/db.py b/bumper/db.py index 71c158a..3b78792 100644 --- a/bumper/db.py +++ b/bumper/db.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 import bumper -from bumper.models import VacBotClient, VacBotDevice, BumperUser, EcoVacsHomeProducts +from bumper.models import VacBotClient, VacBotDevice, BumperUser, EcoVacsHomeProducts, OAuth from tinydb import TinyDB, Query from tinydb.storages import MemoryStorage from datetime import datetime, timedelta @@ -32,6 +32,7 @@ def db_get(): db.table("clients", cache_size=0) db.table("bots", cache_size=0) db.table("tokens", cache_size=0) + db.table("oauth", cache_size=0) return db @@ -202,6 +203,40 @@ def user_revoke_authcode(userid, token, authcode): ) +def user_revoke_expired_oauths(userid): + opendb = db_get() + with opendb: + table = opendb.table("oauth") + search = table.search(Query().userid == userid) + for i in search: + oauth = OAuth(**i) + if datetime.now() >= datetime.fromisoformat(oauth.expire_at): + bumperlog.debug( + "Removing oauth {} due to expiration".format(oauth.access_token) + ) + table.remove(doc_ids=[i.doc_id]) + + +def user_add_oauth(userid) -> OAuth: + user_revoke_expired_oauths(userid) + opendb = db_get() + with opendb: + table = opendb.table("oauth") + entry = table.get(Query().userid == userid) + if entry: + return OAuth(**entry) + else: + oauth = OAuth.create_new(userid) + bumperlog.debug("Adding oauth {} for userid {}".format(oauth.access_token, userid)) + table.insert(oauth.toDB()) + return oauth + + +def token_by_authcode(authcode): + tokens = db_get().table("tokens") + return tokens.get(Query().authcode == authcode) + + def get_disconnected_xmpp_clients(): clients = db_get().table("clients") Client = Query() diff --git a/bumper/models.py b/bumper/models.py index 7177596..917eef6 100644 --- a/bumper/models.py +++ b/bumper/models.py @@ -1,5 +1,9 @@ #!/usr/bin/env python3 import json +import uuid +from datetime import datetime, timedelta + +import bumper class VacBotDevice(object): @@ -83,6 +87,34 @@ class EcoVacsHome_Login(EcoVacs_Login): ucUid = "" +class OAuth: + access_token = "" + expire_at = "" + refresh_token = "" + userId = "" + + def __init__(self, **entries): + self.__dict__.update(entries) + + @classmethod + def create_new(cls, userId: str): + oauth = OAuth() + oauth.userId = userId + oauth.access_token = uuid.uuid4().hex + oauth.expire_at = "{}".format(datetime.utcnow() + timedelta(days=bumper.oauth_validity_days)) + oauth.refresh_token = uuid.uuid4().hex + return oauth + + def toDB(self): + return self.__dict__ + + def toResponse(self): + data = self.__dict__ + data["expire_at"] = bumper.ConfServer.ConfServer_GeneralFunctions().get_milli_time( + datetime.fromisoformat(self.expire_at).timestamp()) + return data + + # EcoVacs Home Product IOT Map - 2020-01-05 # https://portal-ww.ecouser.net/api/pim/product/getProductIotMap EcoVacsHomeProducts = [ diff --git a/bumper/plugins/bumper_confserver_portal_appsvr.py b/bumper/plugins/bumper_confserver_portal_appsvr.py index 2460120..6681d63 100644 --- a/bumper/plugins/bumper_confserver_portal_appsvr.py +++ b/bumper/plugins/bumper_confserver_portal_appsvr.py @@ -17,10 +17,9 @@ class portal_api_appsvr(plugins.ConfServerApp): self.sub_api = "portal_api" self.routes = [ - web.route("*", "/appsvr/app.do", self.handle_appsvr_app, name="portal_api_appsvr_app"), web.route("*", "/appsvr/service/list", self.handle_appsvr_service_list, name="portal_api_appsvr_service_list"), - + web.route("*", "/appsvr/oauth_callback", self.handle_appsvr_oauth_callback, name="portal_api_appsvr_oauth_callback"), ] self.get_milli_time = bumper.ConfServer.ConfServer_GeneralFunctions().get_milli_time @@ -180,4 +179,20 @@ class portal_api_appsvr(plugins.ConfServerApp): except Exception as e: logging.exception("{}".format(e)) + async def handle_appsvr_oauth_callback(self, request): + try: + token = bumper.token_by_authcode(request.query["code"]) + oauth = bumper.user_add_oauth(token["userid"]) + body = { + "code": 0, + "data": oauth.toResponse(), + "ret": "ok", + "todo": "result" + } + + return web.json_response(body) + + except Exception as e: + logging.exception("{}".format(e)) + plugin = portal_api_appsvr() diff --git a/bumper/plugins/bumper_confserver_portal_rapp.py b/bumper/plugins/bumper_confserver_portal_rapp.py new file mode 100644 index 0000000..ca5ef99 --- /dev/null +++ b/bumper/plugins/bumper_confserver_portal_rapp.py @@ -0,0 +1,39 @@ +#!/usr/bin/env python3 +from aiohttp import web +import logging +from bumper.models import * +from bumper import plugins + + +class api_rapp(plugins.ConfServerApp): + + def __init__(self): + self.name = "api_rapp" + self.plugin_type = "sub_api" + self.sub_api = "portal_api" + + self.routes = [ + web.route("*", "/rapp/sds/user/data/map/get", self.handle_map_get, name="api_rapp"), + ] + + self.get_milli_time = bumper.ConfServer.ConfServer_GeneralFunctions().get_milli_time + + async def handle_map_get(self, request): + try: + body = { + "code": 0, + "data": { + "data": { + "name": "My Home" + }, + "tag": None + }, + "message": "success" + } + + return web.json_response(body) + + except Exception as e: + logging.exception("{}".format(e)) + +plugin = api_rapp() diff --git a/tests/test_models.py b/tests/test_models.py new file mode 100644 index 0000000..cd84a47 --- /dev/null +++ b/tests/test_models.py @@ -0,0 +1,14 @@ +from bumper import OAuth + + +def test_oauth(): + userId = "test" + o_auth = OAuth.create_new(userId) + assert o_auth is not None + assert o_auth.userId == userId + assert o_auth.access_token is not None + assert o_auth.expire_at is not None + assert o_auth.refresh_token is not None + + data = o_auth.toResponse() + assert data is not None