Introduce new OAuth2 config flow helper (#27727)
* Refactor the Somfy auth implementation * Typing * Migrate Somfy to OAuth2 flow helper * Add tests * Add more tests * Fix tests * Fix type error * More tests * Remove side effect from constructor * implementation -> auth_implementation * Make get_implementation async * Minor cleanup + Allow picking implementations. * Add support for extra authorize data
This commit is contained in:
parent
6157be23dc
commit
b6c26cb363
15 changed files with 900 additions and 214 deletions
|
@ -1,141 +1,28 @@
|
|||
"""Config flow for Somfy."""
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
import async_timeout
|
||||
|
||||
from homeassistant import config_entries
|
||||
from homeassistant.components.http import HomeAssistantView
|
||||
from homeassistant.core import callback
|
||||
from .const import CLIENT_ID, CLIENT_SECRET, DOMAIN
|
||||
|
||||
AUTH_CALLBACK_PATH = "/auth/somfy/callback"
|
||||
AUTH_CALLBACK_NAME = "auth:somfy:callback"
|
||||
from homeassistant.helpers import config_entry_oauth2_flow
|
||||
from .const import DOMAIN
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@callback
|
||||
def register_flow_implementation(hass, client_id, client_secret):
|
||||
"""Register a flow implementation.
|
||||
@config_entries.HANDLERS.register(DOMAIN)
|
||||
class SomfyFlowHandler(config_entry_oauth2_flow.AbstractOAuth2FlowHandler):
|
||||
"""Config flow to handle Somfy OAuth2 authentication."""
|
||||
|
||||
client_id: Client id.
|
||||
client_secret: Client secret.
|
||||
"""
|
||||
hass.data[DOMAIN][CLIENT_ID] = client_id
|
||||
hass.data[DOMAIN][CLIENT_SECRET] = client_secret
|
||||
|
||||
|
||||
@config_entries.HANDLERS.register("somfy")
|
||||
class SomfyFlowHandler(config_entries.ConfigFlow):
|
||||
"""Handle a config flow."""
|
||||
|
||||
VERSION = 1
|
||||
DOMAIN = DOMAIN
|
||||
CONNECTION_CLASS = config_entries.CONN_CLASS_CLOUD_POLL
|
||||
|
||||
def __init__(self):
|
||||
"""Instantiate config flow."""
|
||||
self.code = None
|
||||
|
||||
async def async_step_import(self, user_input=None):
|
||||
"""Handle external yaml configuration."""
|
||||
if self.hass.config_entries.async_entries(DOMAIN):
|
||||
return self.async_abort(reason="already_setup")
|
||||
return await self.async_step_auth()
|
||||
@property
|
||||
def logger(self) -> logging.Logger:
|
||||
"""Return logger."""
|
||||
return logging.getLogger(__name__)
|
||||
|
||||
async def async_step_user(self, user_input=None):
|
||||
"""Handle a flow start."""
|
||||
if self.hass.config_entries.async_entries(DOMAIN):
|
||||
return self.async_abort(reason="already_setup")
|
||||
|
||||
if DOMAIN not in self.hass.data:
|
||||
return self.async_abort(reason="missing_configuration")
|
||||
|
||||
return await self.async_step_auth()
|
||||
|
||||
async def async_step_auth(self, user_input=None):
|
||||
"""Create an entry for auth."""
|
||||
# Flow has been triggered from Somfy website
|
||||
if user_input:
|
||||
return await self.async_step_code(user_input)
|
||||
|
||||
try:
|
||||
with async_timeout.timeout(10):
|
||||
url, _ = await self._get_authorization_url()
|
||||
except asyncio.TimeoutError:
|
||||
return self.async_abort(reason="authorize_url_timeout")
|
||||
|
||||
return self.async_external_step(step_id="auth", url=url)
|
||||
|
||||
async def _get_authorization_url(self):
|
||||
"""Get Somfy authorization url."""
|
||||
from pymfy.api.somfy_api import SomfyApi
|
||||
|
||||
client_id = self.hass.data[DOMAIN][CLIENT_ID]
|
||||
client_secret = self.hass.data[DOMAIN][CLIENT_SECRET]
|
||||
redirect_uri = f"{self.hass.config.api.base_url}{AUTH_CALLBACK_PATH}"
|
||||
api = SomfyApi(client_id, client_secret, redirect_uri)
|
||||
|
||||
self.hass.http.register_view(SomfyAuthCallbackView())
|
||||
# Thanks to the state, we can forward the flow id to Somfy that will
|
||||
# add it in the callback.
|
||||
return await self.hass.async_add_executor_job(
|
||||
api.get_authorization_url, self.flow_id
|
||||
)
|
||||
|
||||
async def async_step_code(self, code):
|
||||
"""Received code for authentication."""
|
||||
self.code = code
|
||||
return self.async_external_step_done(next_step_id="creation")
|
||||
|
||||
async def async_step_creation(self, user_input=None):
|
||||
"""Create Somfy api and entries."""
|
||||
client_id = self.hass.data[DOMAIN][CLIENT_ID]
|
||||
client_secret = self.hass.data[DOMAIN][CLIENT_SECRET]
|
||||
code = self.code
|
||||
from pymfy.api.somfy_api import SomfyApi
|
||||
|
||||
redirect_uri = f"{self.hass.config.api.base_url}{AUTH_CALLBACK_PATH}"
|
||||
api = SomfyApi(client_id, client_secret, redirect_uri)
|
||||
token = await self.hass.async_add_executor_job(api.request_token, None, code)
|
||||
_LOGGER.info("Successfully authenticated Somfy")
|
||||
return self.async_create_entry(
|
||||
title="Somfy",
|
||||
data={
|
||||
"token": token,
|
||||
"refresh_args": {
|
||||
"client_id": client_id,
|
||||
"client_secret": client_secret,
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
class SomfyAuthCallbackView(HomeAssistantView):
|
||||
"""Somfy Authorization Callback View."""
|
||||
|
||||
requires_auth = False
|
||||
url = AUTH_CALLBACK_PATH
|
||||
name = AUTH_CALLBACK_NAME
|
||||
|
||||
@staticmethod
|
||||
async def get(request):
|
||||
"""Receive authorization code."""
|
||||
from aiohttp import web_response
|
||||
|
||||
if "code" not in request.query or "state" not in request.query:
|
||||
return web_response.Response(
|
||||
text="Missing code or state parameter in " + request.url
|
||||
)
|
||||
|
||||
hass = request.app["hass"]
|
||||
hass.async_create_task(
|
||||
hass.config_entries.flow.async_configure(
|
||||
flow_id=request.query["state"], user_input=request.query["code"]
|
||||
)
|
||||
)
|
||||
|
||||
return web_response.Response(
|
||||
headers={"content-type": "text/html"},
|
||||
text="<script>window.close()</script>",
|
||||
)
|
||||
return await super().async_step_user(user_input)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue