From 3da0ed9cc75596681e78dcb398e212a5befcd35e Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 8 Mar 2019 13:51:42 -0800 Subject: [PATCH] Onboarding to generate auth code (#21777) --- homeassistant/components/auth/__init__.py | 9 +++++++ .../components/onboarding/__init__.py | 2 +- homeassistant/components/onboarding/views.py | 10 ++++++++ tests/components/onboarding/test_views.py | 25 ++++++++++++++++++- 4 files changed, 44 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/auth/__init__.py b/homeassistant/components/auth/__init__.py index 2e74961d11b..8c0c17844f9 100644 --- a/homeassistant/components/auth/__init__.py +++ b/homeassistant/components/auth/__init__.py @@ -127,6 +127,7 @@ import voluptuous as vol from homeassistant.auth.models import User, Credentials, \ TOKEN_TYPE_LONG_LIVED_ACCESS_TOKEN +from homeassistant.loader import bind_hass from homeassistant.components import websocket_api from homeassistant.components.http import KEY_REAL_IP from homeassistant.components.http.auth import async_sign_path @@ -184,10 +185,18 @@ RESULT_TYPE_USER = 'user' _LOGGER = logging.getLogger(__name__) +@bind_hass +def create_auth_code(hass, client_id: str, user: User) -> str: + """Create an authorization code to fetch tokens.""" + return hass.data[DOMAIN](client_id, user) + + async def async_setup(hass, config): """Component to allow users to login.""" store_result, retrieve_result = _create_auth_code_store() + hass.data[DOMAIN] = store_result + hass.http.register_view(TokenView(retrieve_result)) hass.http.register_view(LinkUserView(retrieve_result)) diff --git a/homeassistant/components/onboarding/__init__.py b/homeassistant/components/onboarding/__init__.py index 6bbe546dcb1..f8885962ee7 100644 --- a/homeassistant/components/onboarding/__init__.py +++ b/homeassistant/components/onboarding/__init__.py @@ -4,7 +4,7 @@ from homeassistant.loader import bind_hass from .const import DOMAIN, STEP_USER, STEPS -DEPENDENCIES = ['http'] +DEPENDENCIES = ['auth', 'http'] STORAGE_KEY = DOMAIN STORAGE_VERSION = 1 diff --git a/homeassistant/components/onboarding/views.py b/homeassistant/components/onboarding/views.py index 804589200fa..d9631b77a20 100644 --- a/homeassistant/components/onboarding/views.py +++ b/homeassistant/components/onboarding/views.py @@ -74,6 +74,7 @@ class UserOnboardingView(_BaseOnboardingView): vol.Required('name'): str, vol.Required('username'): str, vol.Required('password'): str, + vol.Required('client_id'): str, })) async def post(self, request, data): """Return the manifest.json.""" @@ -98,8 +99,17 @@ class UserOnboardingView(_BaseOnboardingView): await hass.components.person.async_create_person( data['name'], user_id=user.id ) + await self._async_mark_done(hass) + # Return an authorization code to allow fetching tokens. + auth_code = hass.components.auth.create_auth_code( + data['client_id'], user + ) + return self.json({ + 'auth_code': auth_code + }) + @callback def _async_get_hass_provider(hass): diff --git a/tests/components/onboarding/test_views.py b/tests/components/onboarding/test_views.py index 5b303943747..fdf472f3b13 100644 --- a/tests/components/onboarding/test_views.py +++ b/tests/components/onboarding/test_views.py @@ -8,7 +8,7 @@ from homeassistant.setup import async_setup_component from homeassistant.components import onboarding from homeassistant.components.onboarding import views -from tests.common import register_auth_provider +from tests.common import CLIENT_ID, register_auth_provider from . import mock_storage @@ -59,6 +59,7 @@ async def test_onboarding_user_already_done(hass, hass_storage, client = await aiohttp_client(hass.http.app) resp = await client.post('/api/onboarding/users', json={ + 'client_id': CLIENT_ID, 'name': 'Test Name', 'username': 'test-user', 'password': 'test-pass', @@ -79,12 +80,16 @@ async def test_onboarding_user(hass, hass_storage, aiohttp_client): client = await aiohttp_client(hass.http.app) resp = await client.post('/api/onboarding/users', json={ + 'client_id': CLIENT_ID, 'name': 'Test Name', 'username': 'test-user', 'password': 'test-pass', }) assert resp.status == 200 + data = await resp.json() + assert 'auth_code' in data + users = await hass.auth.async_get_users() assert len(users) == 1 user = users[0] @@ -93,6 +98,21 @@ async def test_onboarding_user(hass, hass_storage, aiohttp_client): assert user.credentials[0].data['username'] == 'test-user' assert len(hass.data['person'].storage_data) == 1 + # Request refresh tokens + resp = await client.post('/auth/token', data={ + 'client_id': CLIENT_ID, + 'grant_type': 'authorization_code', + 'code': data['auth_code'] + }) + + assert resp.status == 200 + tokens = await resp.json() + + assert ( + await hass.auth.async_validate_access_token(tokens['access_token']) + is not None + ) + async def test_onboarding_user_invalid_name(hass, hass_storage, aiohttp_client): @@ -106,6 +126,7 @@ async def test_onboarding_user_invalid_name(hass, hass_storage, client = await aiohttp_client(hass.http.app) resp = await client.post('/api/onboarding/users', json={ + 'client_id': CLIENT_ID, 'username': 'test-user', 'password': 'test-pass', }) @@ -124,11 +145,13 @@ async def test_onboarding_user_race(hass, hass_storage, aiohttp_client): client = await aiohttp_client(hass.http.app) resp1 = client.post('/api/onboarding/users', json={ + 'client_id': CLIENT_ID, 'name': 'Test 1', 'username': '1-user', 'password': '1-pass', }) resp2 = client.post('/api/onboarding/users', json={ + 'client_id': CLIENT_ID, 'name': 'Test 2', 'username': '2-user', 'password': '2-pass',