From 9399c8e11a190dc7643b12159dca211b489192c4 Mon Sep 17 00:00:00 2001 From: Matthieu Date: Sat, 2 Nov 2019 16:04:44 +0100 Subject: [PATCH] Add login failed treatment and renew token --- README.md | 31 ++++- .../georide/.translations/en.json | 12 +- .../georide/.translations/fr.json | 25 ++++ custom_components/georide/__init__.py | 109 +++++++++++------- custom_components/georide/config_flow.py | 75 ++++++------ custom_components/georide/const.py | 3 + custom_components/georide/device_tracker.py | 13 +-- custom_components/georide/manifest.json | 2 +- custom_components/georide/strings.json | 12 +- custom_components/georide/switch.py | 12 +- hacs.json | 3 +- 11 files changed, 193 insertions(+), 104 deletions(-) create mode 100644 custom_components/georide/.translations/fr.json diff --git a/README.md b/README.md index 588849c..731dd5e 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,33 @@ # Georide Home assistant -![Logo Georide](georide-logo.png) +![Logo Georide](./georide-logo.png) -⚠️ this is not an official implementation +⚠️ This is not an official implementation -Official georide website: https://georide.fr/ +[![hacs_badge](https://img.shields.io/badge/HACS-Custom-orange.svg?style=for-the-badge)](https://github.com/custom-components/hacs) +[![License: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg?style=for-the-badge)](https://www.gnu.org/licenses/gpl-3.0) +Official GeoRide website: https://georide.fr/ +## Description +This component add some sensor for GeoRide Tracker + + +## Options + +| Name | Type | Requirement | `default` Description +| ---- | ---- | ------- | ----------- +| email | string | **Required** | GeoRide email +| password | string | **Required** | GeoRide password + + +## Installation +### Option 1 +- Just folow the integration config steps. + +### Option 2 +- Add the folowing line in your configuration.yml +```yaml + georide: + email: @exmple.com + password: +``` \ No newline at end of file diff --git a/custom_components/georide/.translations/en.json b/custom_components/georide/.translations/en.json index 8331263..ce32717 100644 --- a/custom_components/georide/.translations/en.json +++ b/custom_components/georide/.translations/en.json @@ -2,19 +2,21 @@ "config": { "title": "GeoRide", "step": { - "user": { + "georide_login": { "title": "Set up GeoRide", - "description": "Are you sure you want to set up GeoRide ?", + "description": "Sign-in to georide", "data": { "email": "email", "password": "password" } } }, + "error": { + "faulty_credentials": "Authorization failed", + "unknown": "Unknown error" + }, "abort": { - "one_instance_allowed": "Only a single instance is allowed.", - "no_credential": "You need to add your credentails." - + "one_instance_allowed": "Only a single instance is allowed." }, "create_entry": { "default": "\n\nLogin succes" diff --git a/custom_components/georide/.translations/fr.json b/custom_components/georide/.translations/fr.json new file mode 100644 index 0000000..53c5899 --- /dev/null +++ b/custom_components/georide/.translations/fr.json @@ -0,0 +1,25 @@ +{ + "config": { + "title": "GeoRide", + "step": { + "georide_login": { + "title": "Configuration de GeoRide", + "description": "T'es un mortard ! V", + "data": { + "email": "email", + "password": "password" + } + } + }, + "error": { + "faulty_credentials": "Connexion \u00e9chou\u00e9", + "unkonwn": "Erreur inconue" + }, + "abort": { + "one_instance_allowed": "Seulement un instance est authoris\u00e9e" + }, + "create_entry": { + "default": "\n\nConnexion r\u00e9ussie!" + } + } +} \ No newline at end of file diff --git a/custom_components/georide/__init__.py b/custom_components/georide/__init__.py index 246346f..fca6175 100644 --- a/custom_components/georide/__init__.py +++ b/custom_components/georide/__init__.py @@ -3,14 +3,17 @@ from collections import defaultdict import logging from datetime import timedelta -import voluptuous as vol +import math +import time import json -import jwt from threading import Thread +import voluptuous as vol +import jwt from aiohttp.web import json_response from georideapilib.objects import GeorideAccount import georideapilib.api as GeorideApi + from georideapilib.socket import GeorideSocket @@ -25,10 +28,12 @@ from homeassistant.setup import async_when_setup from .const import ( CONF_EMAIL, CONF_PASSWORD, - TRACKER_ID + CONF_TOKEN, + TRACKER_ID, + TOKEN_SAFE_DAY, + DOMAIN ) -DOMAIN = "georide" _LOGGER = logging.getLogger(__name__) @@ -47,12 +52,16 @@ CONFIG_SCHEMA = vol.Schema( async def async_setup(hass, config): """Setup Georide component.""" hass.data[DOMAIN] = {"config": config[DOMAIN], "devices": {}, "unsub": None} - result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": config_entries.SOURCE_IMPORT}, data={} + hass.async_create_task( + hass.config_entries.flow.async_init( + DOMAIN, + context={ + "source": config_entries.SOURCE_IMPORT + }, + data={} + ) ) - _LOGGER.info("Georide-setup success: %s", result) - # Return boolean to indicate that initialization was successful. return True @@ -66,6 +75,8 @@ def connect_socket(hass, component): socket.subscribe_device(context.on_device_callback) socket.subscribe_position(context.on_position_callback) + context.socket = socket + socket.init() socket.connect(context.async_get_token()) @@ -75,34 +86,32 @@ async def async_setup_entry(hass, entry): config = hass.data[DOMAIN]["config"] email = config.get(CONF_EMAIL) or entry.data[CONF_EMAIL] password = config.get(CONF_PASSWORD) or entry.data[CONF_PASSWORD] + token = config.get(CONF_TOKEN) or entry.data[CONF_TOKEN] + + _LOGGER.info("Georide token: %s",token) + context = GeorideContext( + hass, + email, + password, + token + ) - if email is None or password is None: - return False - try: - account = GeorideApi.get_authorisation_token(email, password) - context = GeorideContext( - hass, - email, - password, - account.auth_token - ) + hass.data[DOMAIN]["context"] = context + + # We add trackers to the context + trackers = GeorideApi.get_trackers(token) + context.georide_trackers = trackers - hass.data[DOMAIN]["context"] = context + hass.async_create_task( + hass.config_entries.async_forward_entry_setup(entry, "device_tracker")) + hass.async_create_task( + hass.config_entries.async_forward_entry_setup(entry, "switch")) - # We add trackers to the context - trackers = GeorideApi.get_trackers(account.auth_token) - context.georide_trackers = trackers + thread = Thread(target=connect_socket, args=(hass, entry)) + thread.start() - hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, "device_tracker")) - hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, "switch")) - thread = Thread(target=connect_socket, args=(hass, entry)) - thread.start() - except: - return False return True async def async_unload_entry(hass, entry): @@ -110,6 +119,8 @@ async def async_unload_entry(hass, entry): await hass.config_entries.async_forward_entry_unload(entry, "device_tracker") await hass.config_entries.async_forward_entry_unload(entry, "switch") + context = hass.data[DOMAIN]["context"] + context.socket.disconnect() hass.data[DOMAIN]["unsub"]() @@ -126,7 +137,7 @@ class GeorideContext: self._password = password self._georide_trackers = defaultdict(set) self._token = token - self._pending_msg = [] + self._socket = None @property def hass(self): @@ -156,29 +167,43 @@ class GeorideContext: @georide_trackers.setter def georide_trackers(self, trackers): """ georide tracker list """ - self._georide_trackers = trackers + self._georide_trackers = trackers - @callback - def async_get_token(self): - """ here we return the current valid tocken, TODO: add here token expiration control""" + def get_token(self): + """ here we return the current valid tocken """ jwt_data = jwt.decode(self._token, verify=False) exp_timestamp = jwt_data['exp'] + + epoch = math.ceil(time.time()) + + if (exp_timestamp - TOKEN_SAFE_DAY) < epoch: + _LOGGER.info("Time reached, renew token") + account = GeorideApi.get_authorisation_token(self._email, self._password) + config = self._hass.data[DOMAIN]["config"] + config[CONF_TOKEN] = account.auth_token + self._token = account.auth_token + + _LOGGER.info("Token exp data: %s", exp_timestamp) return self._token - @callback - def async_get_tracker(self, tracker_id): + def get_tracker(self, tracker_id): """ here we return last tracker by id""" for tracker in self._georide_trackers: if tracker.tracker_id == tracker_id: return tracker return None - @callback - def async_see(self, **data): - """Send a see message to the device tracker.""" - _LOGGER.info("sync_see") - self._pending_msg.append(data) + @property + def socket(self): + """ hold the georide socket """ + return self._socket + + @socket.setter + def socket(self, socket): + """set the georide socket""" + self._socket = socket + @callback def on_lock_callback(self, data): diff --git a/custom_components/georide/config_flow.py b/custom_components/georide/config_flow.py index fc62df5..147362b 100644 --- a/custom_components/georide/config_flow.py +++ b/custom_components/georide/config_flow.py @@ -3,9 +3,11 @@ import logging from homeassistant import config_entries import voluptuous as vol +import georideapilib.api as GeorideApi +import georideapilib.exception as GeorideException -from .const import CONF_EMAIL, CONF_PASSWORD +from .const import CONF_EMAIL, CONF_PASSWORD, CONF_TOKEN _LOGGER = logging.getLogger(__name__) @@ -14,47 +16,52 @@ _LOGGER = logging.getLogger(__name__) class GeorideConfigFlow(config_entries.ConfigFlow): """Georide config flow """ - async def async_step_user(self, user_input=None): + async def async_step_user(self, user_input=None): #pylint: disable=W0613 """ handle info to help to configure georide """ if self._async_current_entries(): return self.async_abort(reason="one_instance_allowed") - if user_input is None: - return self.async_show_form(step_id='user', data_schema=vol.Schema({ - vol.Required(CONF_EMAIL): vol.All(str, vol.Length(min=3)), - vol.Required(CONF_PASSWORD): vol.All(str) - })) + return self.async_show_form(step_id='georide_login', data_schema=vol.Schema({ + vol.Required(CONF_EMAIL): vol.All(str, vol.Length(min=3)), + vol.Required(CONF_PASSWORD): vol.All(str) + })) - return self.async_create_entry( - title=user_input[CONF_EMAIL], - data={ - CONF_EMAIL: user_input[CONF_EMAIL], - CONF_PASSWORD: user_input[CONF_PASSWORD] - } - ) - - - - async def async_step_import(self, user_input): + async def async_step_import(self, user_input=None): #pylint: disable=W0613 """Import a config flow from configuration.""" - - _LOGGER.info("user email: %", str(user_input)) - if self._async_current_entries(): return self.async_abort(reason="one_instance_allowed") - if user_input is None: - return self.async_show_form(step_id='user', data_schema=vol.Schema({ - vol.Required(CONF_EMAIL): vol.All(str, vol.Length(min=3)), - vol.Required(CONF_PASSWORD): vol.All(str) - })) - - return self.async_create_entry( - title=user_input, - data={ - CONF_EMAIL: user_input[CONF_EMAIL], - CONF_PASSWORD: user_input[CONF_PASSWORD] - } - ) + return self.async_show_form(step_id='georide_login', data_schema=vol.Schema({ + vol.Required(CONF_EMAIL): vol.All(str, vol.Length(min=3)), + vol.Required(CONF_PASSWORD): vol.All(str) + })) + + + + async def async_step_georide_login(self, user_input): + """ try to seupt GeoRide Account """ + errors = {} + try: + account = GeorideApi.get_authorisation_token( + user_input[CONF_EMAIL], + user_input[CONF_PASSWORD]) + return self.async_create_entry( + title=user_input[CONF_EMAIL], + data={ + CONF_EMAIL: user_input[CONF_EMAIL], + CONF_PASSWORD: user_input[CONF_PASSWORD], + CONF_TOKEN: account.auth_token + } + ) + except (GeorideException.SeverException, GeorideException.LoginException): + _LOGGER.error("Invalid credentials provided, config not created") + errors["base"] = "faulty_credentials" + return self.async_show_form(step_id="georide_login", errors=errors) + except: + _LOGGER.error("Unknown error") + errors["base"] = "faulty_credentials" + return self.async_show_form(step_id="georide_login", errors=errors) + + \ No newline at end of file diff --git a/custom_components/georide/const.py b/custom_components/georide/const.py index 81a1586..9d623a7 100644 --- a/custom_components/georide/const.py +++ b/custom_components/georide/const.py @@ -1,4 +1,5 @@ """ Georide const file """ +DOMAIN = "georide" CONF_EMAIL = "email" CONF_PASSWORD = "password" @@ -6,3 +7,5 @@ CONF_PASSWORD = "password" CONF_TOKEN = "token" TRACKER_ID = "trackerId" + +TOKEN_SAFE_DAY = 432000 # five days diff --git a/custom_components/georide/device_tracker.py b/custom_components/georide/device_tracker.py index d4893d0..9c557f1 100644 --- a/custom_components/georide/device_tracker.py +++ b/custom_components/georide/device_tracker.py @@ -2,13 +2,12 @@ import logging -from homeassistant.core import callback from homeassistant.components.device_tracker.const import ENTITY_ID_FORMAT, SOURCE_TYPE_GPS from homeassistant.components.device_tracker.config_entry import TrackerEntity import georideapilib.api as GeorideApi -from . import DOMAIN as GEORIDE_DOMAIN +from .const import DOMAIN as GEORIDE_DOMAIN _LOGGER = logging.getLogger(__name__) @@ -18,19 +17,19 @@ async def async_setup_entry(hass, config_entry, async_add_entities): # pylint: d georide_context = hass.data[GEORIDE_DOMAIN]["context"] - if georide_context.token is None: + if georide_context.get_token() is None: return False - _LOGGER.info('Current georide token: %s', georide_context.async_get_token()) + _LOGGER.info('Current georide token: %s', georide_context.get_token()) - trackers = GeorideApi.get_trackers(georide_context.async_get_token()) + trackers = GeorideApi.get_trackers(georide_context.get_token()) tracker_entities = [] for tracker in trackers: - entity = GeorideTrackerEntity(tracker.tracker_id, georide_context.async_get_token, - georide_context.async_get_tracker, tracker) + entity = GeorideTrackerEntity(tracker.tracker_id, georide_context.get_token, + georide_context.get_tracker, tracker) hass.data[GEORIDE_DOMAIN]["devices"][tracker.tracker_id] = entity diff --git a/custom_components/georide/manifest.json b/custom_components/georide/manifest.json index 2e70a63..7a11df4 100644 --- a/custom_components/georide/manifest.json +++ b/custom_components/georide/manifest.json @@ -4,7 +4,7 @@ "config_flow": true, "documentation": "https://git.tontontux.fr/mduval/GeorideHA", "requirements": [ - "georideapilib>=0.4.1", + "georideapilib>=0.4.3", "pyjwt>=1.7.1" ], "dependencies": [], diff --git a/custom_components/georide/strings.json b/custom_components/georide/strings.json index 8331263..ce32717 100644 --- a/custom_components/georide/strings.json +++ b/custom_components/georide/strings.json @@ -2,19 +2,21 @@ "config": { "title": "GeoRide", "step": { - "user": { + "georide_login": { "title": "Set up GeoRide", - "description": "Are you sure you want to set up GeoRide ?", + "description": "Sign-in to georide", "data": { "email": "email", "password": "password" } } }, + "error": { + "faulty_credentials": "Authorization failed", + "unknown": "Unknown error" + }, "abort": { - "one_instance_allowed": "Only a single instance is allowed.", - "no_credential": "You need to add your credentails." - + "one_instance_allowed": "Only a single instance is allowed." }, "create_entry": { "default": "\n\nLogin succes" diff --git a/custom_components/georide/switch.py b/custom_components/georide/switch.py index e0e030f..d47a550 100644 --- a/custom_components/georide/switch.py +++ b/custom_components/georide/switch.py @@ -8,7 +8,7 @@ from homeassistant.components.switch import ENTITY_ID_FORMAT import georideapilib.api as GeorideApi -from . import DOMAIN as GEORIDE_DOMAIN +from .const import DOMAIN as GEORIDE_DOMAIN _LOGGER = logging.getLogger(__name__) @@ -18,17 +18,17 @@ async def async_setup_entry(hass, config_entry, async_add_entities): # pylint: d """Set up Georide tracker based off an entry.""" georide_context = hass.data[GEORIDE_DOMAIN]["context"] - if georide_context.async_get_token() is None: + if georide_context.get_token() is None: return False - _LOGGER.info('Current georide token: %s', georide_context.async_get_token()) - trackers = GeorideApi.get_trackers(georide_context.async_get_token()) + _LOGGER.info('Current georide token: %s', georide_context.get_token()) + trackers = GeorideApi.get_trackers(georide_context.get_token()) lock_switch_entities = [] for tracker in trackers: - entity = GeorideLockSwitchEntity(tracker.tracker_id, georide_context.async_get_token, - georide_context.async_get_tracker, data=tracker) + entity = GeorideLockSwitchEntity(tracker.tracker_id, georide_context.get_token, + georide_context.get_tracker, data=tracker) hass.data[GEORIDE_DOMAIN]["devices"][tracker.tracker_id] = entity lock_switch_entities.append(entity) diff --git a/hacs.json b/hacs.json index fe7f203..24fd578 100644 --- a/hacs.json +++ b/hacs.json @@ -3,5 +3,6 @@ "content_in_root": false, "render_readme": true, "domains": ["devices_tracker", "sensor"], - "country": ["FR"] + "country": ["FR"], + "homeassistant": "0.100.0" } \ No newline at end of file