2018-05-01 16:20:41 +00:00
|
|
|
"""Tests for the Home Assistant auth module."""
|
2018-06-29 04:02:33 +00:00
|
|
|
from datetime import timedelta
|
|
|
|
from unittest.mock import Mock, patch
|
2018-05-01 16:20:41 +00:00
|
|
|
|
|
|
|
import pytest
|
|
|
|
|
|
|
|
from homeassistant import auth, data_entry_flow
|
2018-07-13 09:43:08 +00:00
|
|
|
from homeassistant.auth import (
|
|
|
|
models as auth_models, auth_store, const as auth_const)
|
2018-06-29 04:02:33 +00:00
|
|
|
from homeassistant.util import dt as dt_util
|
2018-07-09 16:24:46 +00:00
|
|
|
from tests.common import (
|
|
|
|
MockUser, ensure_auth_manager_loaded, flush_store, CLIENT_ID)
|
2018-05-01 16:20:41 +00:00
|
|
|
|
|
|
|
|
|
|
|
@pytest.fixture
|
2018-06-29 04:02:33 +00:00
|
|
|
def mock_hass(loop):
|
2018-05-01 16:20:41 +00:00
|
|
|
"""Hass mock with minimum amount of data set to make it work with auth."""
|
|
|
|
hass = Mock()
|
|
|
|
hass.config.skip_pip = True
|
|
|
|
return hass
|
|
|
|
|
|
|
|
|
|
|
|
async def test_auth_manager_from_config_validates_config_and_id(mock_hass):
|
|
|
|
"""Test get auth providers."""
|
|
|
|
manager = await auth.auth_manager_from_config(mock_hass, [{
|
|
|
|
'name': 'Test Name',
|
|
|
|
'type': 'insecure_example',
|
|
|
|
'users': [],
|
|
|
|
}, {
|
|
|
|
'name': 'Invalid config because no users',
|
|
|
|
'type': 'insecure_example',
|
|
|
|
'id': 'invalid_config',
|
|
|
|
}, {
|
|
|
|
'name': 'Test Name 2',
|
|
|
|
'type': 'insecure_example',
|
|
|
|
'id': 'another',
|
|
|
|
'users': [],
|
|
|
|
}, {
|
|
|
|
'name': 'Wrong because duplicate ID',
|
|
|
|
'type': 'insecure_example',
|
|
|
|
'id': 'another',
|
|
|
|
'users': [],
|
|
|
|
}])
|
|
|
|
|
|
|
|
providers = [{
|
|
|
|
'name': provider.name,
|
|
|
|
'id': provider.id,
|
|
|
|
'type': provider.type,
|
2018-07-13 13:31:20 +00:00
|
|
|
} for provider in manager.auth_providers]
|
2018-05-01 16:20:41 +00:00
|
|
|
assert providers == [{
|
|
|
|
'name': 'Test Name',
|
|
|
|
'type': 'insecure_example',
|
|
|
|
'id': None,
|
|
|
|
}, {
|
|
|
|
'name': 'Test Name 2',
|
|
|
|
'type': 'insecure_example',
|
|
|
|
'id': 'another',
|
|
|
|
}]
|
|
|
|
|
|
|
|
|
2018-06-29 02:14:26 +00:00
|
|
|
async def test_create_new_user(hass, hass_storage):
|
2018-05-01 16:20:41 +00:00
|
|
|
"""Test creating new user."""
|
2018-06-29 02:14:26 +00:00
|
|
|
manager = await auth.auth_manager_from_config(hass, [{
|
2018-05-01 16:20:41 +00:00
|
|
|
'type': 'insecure_example',
|
|
|
|
'users': [{
|
|
|
|
'username': 'test-user',
|
|
|
|
'password': 'test-pass',
|
|
|
|
'name': 'Test Name'
|
|
|
|
}]
|
|
|
|
}])
|
|
|
|
|
|
|
|
step = await manager.login_flow.async_init(('insecure_example', None))
|
|
|
|
assert step['type'] == data_entry_flow.RESULT_TYPE_FORM
|
|
|
|
|
|
|
|
step = await manager.login_flow.async_configure(step['flow_id'], {
|
|
|
|
'username': 'test-user',
|
|
|
|
'password': 'test-pass',
|
|
|
|
})
|
|
|
|
assert step['type'] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
|
|
|
|
credentials = step['result']
|
|
|
|
user = await manager.async_get_or_create_user(credentials)
|
|
|
|
assert user is not None
|
|
|
|
assert user.is_owner is True
|
|
|
|
assert user.name == 'Test Name'
|
|
|
|
|
|
|
|
|
|
|
|
async def test_login_as_existing_user(mock_hass):
|
|
|
|
"""Test login as existing user."""
|
|
|
|
manager = await auth.auth_manager_from_config(mock_hass, [{
|
|
|
|
'type': 'insecure_example',
|
|
|
|
'users': [{
|
|
|
|
'username': 'test-user',
|
|
|
|
'password': 'test-pass',
|
|
|
|
'name': 'Test Name'
|
|
|
|
}]
|
|
|
|
}])
|
|
|
|
ensure_auth_manager_loaded(manager)
|
|
|
|
|
2018-07-10 18:33:03 +00:00
|
|
|
# Add a fake user that we're not going to log in with
|
|
|
|
user = MockUser(
|
|
|
|
id='mock-user2',
|
|
|
|
is_owner=False,
|
|
|
|
is_active=False,
|
|
|
|
name='Not user',
|
|
|
|
).add_to_auth_manager(manager)
|
2018-07-13 09:43:08 +00:00
|
|
|
user.credentials.append(auth_models.Credentials(
|
2018-07-10 18:33:03 +00:00
|
|
|
id='mock-id2',
|
|
|
|
auth_provider_type='insecure_example',
|
|
|
|
auth_provider_id=None,
|
|
|
|
data={'username': 'other-user'},
|
|
|
|
is_new=False,
|
|
|
|
))
|
|
|
|
|
2018-05-01 16:20:41 +00:00
|
|
|
# Add fake user with credentials for example auth provider.
|
|
|
|
user = MockUser(
|
|
|
|
id='mock-user',
|
|
|
|
is_owner=False,
|
|
|
|
is_active=False,
|
|
|
|
name='Paulus',
|
|
|
|
).add_to_auth_manager(manager)
|
2018-07-13 09:43:08 +00:00
|
|
|
user.credentials.append(auth_models.Credentials(
|
2018-05-01 16:20:41 +00:00
|
|
|
id='mock-id',
|
|
|
|
auth_provider_type='insecure_example',
|
|
|
|
auth_provider_id=None,
|
|
|
|
data={'username': 'test-user'},
|
|
|
|
is_new=False,
|
|
|
|
))
|
|
|
|
|
|
|
|
step = await manager.login_flow.async_init(('insecure_example', None))
|
|
|
|
assert step['type'] == data_entry_flow.RESULT_TYPE_FORM
|
|
|
|
|
|
|
|
step = await manager.login_flow.async_configure(step['flow_id'], {
|
|
|
|
'username': 'test-user',
|
|
|
|
'password': 'test-pass',
|
|
|
|
})
|
|
|
|
assert step['type'] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
|
|
|
|
credentials = step['result']
|
|
|
|
|
|
|
|
user = await manager.async_get_or_create_user(credentials)
|
|
|
|
assert user is not None
|
|
|
|
assert user.id == 'mock-user'
|
|
|
|
assert user.is_owner is False
|
|
|
|
assert user.is_active is False
|
|
|
|
assert user.name == 'Paulus'
|
|
|
|
|
|
|
|
|
2018-06-29 02:14:26 +00:00
|
|
|
async def test_linking_user_to_two_auth_providers(hass, hass_storage):
|
2018-05-01 16:20:41 +00:00
|
|
|
"""Test linking user to two auth providers."""
|
2018-06-29 02:14:26 +00:00
|
|
|
manager = await auth.auth_manager_from_config(hass, [{
|
2018-05-01 16:20:41 +00:00
|
|
|
'type': 'insecure_example',
|
|
|
|
'users': [{
|
|
|
|
'username': 'test-user',
|
|
|
|
'password': 'test-pass',
|
|
|
|
}]
|
|
|
|
}, {
|
|
|
|
'type': 'insecure_example',
|
|
|
|
'id': 'another-provider',
|
|
|
|
'users': [{
|
|
|
|
'username': 'another-user',
|
|
|
|
'password': 'another-password',
|
|
|
|
}]
|
|
|
|
}])
|
|
|
|
|
|
|
|
step = await manager.login_flow.async_init(('insecure_example', None))
|
|
|
|
step = await manager.login_flow.async_configure(step['flow_id'], {
|
|
|
|
'username': 'test-user',
|
|
|
|
'password': 'test-pass',
|
|
|
|
})
|
|
|
|
user = await manager.async_get_or_create_user(step['result'])
|
|
|
|
assert user is not None
|
|
|
|
|
|
|
|
step = await manager.login_flow.async_init(('insecure_example',
|
|
|
|
'another-provider'))
|
|
|
|
step = await manager.login_flow.async_configure(step['flow_id'], {
|
|
|
|
'username': 'another-user',
|
|
|
|
'password': 'another-password',
|
|
|
|
})
|
|
|
|
await manager.async_link_user(user, step['result'])
|
|
|
|
assert len(user.credentials) == 2
|
2018-06-29 02:14:26 +00:00
|
|
|
|
|
|
|
|
|
|
|
async def test_saving_loading(hass, hass_storage):
|
|
|
|
"""Test storing and saving data.
|
|
|
|
|
|
|
|
Creates one of each type that we store to test we restore correctly.
|
|
|
|
"""
|
|
|
|
manager = await auth.auth_manager_from_config(hass, [{
|
|
|
|
'type': 'insecure_example',
|
|
|
|
'users': [{
|
|
|
|
'username': 'test-user',
|
|
|
|
'password': 'test-pass',
|
|
|
|
}]
|
|
|
|
}])
|
|
|
|
|
|
|
|
step = await manager.login_flow.async_init(('insecure_example', None))
|
|
|
|
step = await manager.login_flow.async_configure(step['flow_id'], {
|
|
|
|
'username': 'test-user',
|
|
|
|
'password': 'test-pass',
|
|
|
|
})
|
|
|
|
user = await manager.async_get_or_create_user(step['result'])
|
|
|
|
|
2018-07-09 16:24:46 +00:00
|
|
|
refresh_token = await manager.async_create_refresh_token(user, CLIENT_ID)
|
2018-06-29 02:14:26 +00:00
|
|
|
|
|
|
|
manager.async_create_access_token(refresh_token)
|
|
|
|
|
|
|
|
await flush_store(manager._store._store)
|
|
|
|
|
2018-07-13 09:43:08 +00:00
|
|
|
store2 = auth_store.AuthStore(hass)
|
2018-07-01 17:36:50 +00:00
|
|
|
users = await store2.async_get_users()
|
|
|
|
assert len(users) == 1
|
|
|
|
assert users[0] == user
|
2018-06-29 02:14:26 +00:00
|
|
|
|
2018-06-29 04:02:33 +00:00
|
|
|
|
|
|
|
def test_access_token_expired():
|
|
|
|
"""Test that the expired property on access tokens work."""
|
2018-07-13 09:43:08 +00:00
|
|
|
refresh_token = auth_models.RefreshToken(
|
2018-06-29 04:02:33 +00:00
|
|
|
user=None,
|
|
|
|
client_id='bla'
|
|
|
|
)
|
|
|
|
|
2018-07-13 09:43:08 +00:00
|
|
|
access_token = auth_models.AccessToken(
|
2018-06-29 04:02:33 +00:00
|
|
|
refresh_token=refresh_token
|
|
|
|
)
|
|
|
|
|
|
|
|
assert access_token.expired is False
|
|
|
|
|
2018-07-13 09:43:08 +00:00
|
|
|
with patch('homeassistant.util.dt.utcnow',
|
|
|
|
return_value=dt_util.utcnow() +
|
|
|
|
auth_const.ACCESS_TOKEN_EXPIRATION):
|
2018-06-29 04:02:33 +00:00
|
|
|
assert access_token.expired is True
|
|
|
|
|
2018-07-13 09:43:08 +00:00
|
|
|
almost_exp = \
|
|
|
|
dt_util.utcnow() + auth_const.ACCESS_TOKEN_EXPIRATION - timedelta(1)
|
|
|
|
with patch('homeassistant.util.dt.utcnow', return_value=almost_exp):
|
2018-06-29 04:02:33 +00:00
|
|
|
assert access_token.expired is False
|
|
|
|
|
|
|
|
|
|
|
|
async def test_cannot_retrieve_expired_access_token(hass):
|
|
|
|
"""Test that we cannot retrieve expired access tokens."""
|
|
|
|
manager = await auth.auth_manager_from_config(hass, [])
|
2018-07-04 15:50:08 +00:00
|
|
|
user = MockUser().add_to_auth_manager(manager)
|
2018-07-09 16:24:46 +00:00
|
|
|
refresh_token = await manager.async_create_refresh_token(user, CLIENT_ID)
|
2018-07-01 17:36:50 +00:00
|
|
|
assert refresh_token.user.id is user.id
|
2018-07-09 16:24:46 +00:00
|
|
|
assert refresh_token.client_id == CLIENT_ID
|
2018-06-29 04:02:33 +00:00
|
|
|
|
2018-07-01 17:36:50 +00:00
|
|
|
access_token = manager.async_create_access_token(refresh_token)
|
2018-06-29 04:02:33 +00:00
|
|
|
assert manager.async_get_access_token(access_token.token) is access_token
|
|
|
|
|
2018-07-13 09:43:08 +00:00
|
|
|
with patch('homeassistant.util.dt.utcnow',
|
|
|
|
return_value=dt_util.utcnow() +
|
|
|
|
auth_const.ACCESS_TOKEN_EXPIRATION):
|
2018-06-29 04:02:33 +00:00
|
|
|
assert manager.async_get_access_token(access_token.token) is None
|
|
|
|
|
|
|
|
# Even with unpatched time, it should have been removed from manager
|
|
|
|
assert manager.async_get_access_token(access_token.token) is None
|
2018-07-01 17:36:50 +00:00
|
|
|
|
|
|
|
|
2018-07-04 15:50:08 +00:00
|
|
|
async def test_generating_system_user(hass):
|
|
|
|
"""Test that we can add a system user."""
|
2018-07-01 17:36:50 +00:00
|
|
|
manager = await auth.auth_manager_from_config(hass, [])
|
2018-07-04 15:50:08 +00:00
|
|
|
user = await manager.async_create_system_user('Hass.io')
|
|
|
|
token = await manager.async_create_refresh_token(user)
|
|
|
|
assert user.system_generated
|
|
|
|
assert token is not None
|
|
|
|
assert token.client_id is None
|
|
|
|
|
|
|
|
|
|
|
|
async def test_refresh_token_requires_client_for_user(hass):
|
|
|
|
"""Test that we can add a system user."""
|
|
|
|
manager = await auth.auth_manager_from_config(hass, [])
|
|
|
|
user = MockUser().add_to_auth_manager(manager)
|
|
|
|
assert user.system_generated is False
|
|
|
|
|
2018-07-01 17:36:50 +00:00
|
|
|
with pytest.raises(ValueError):
|
2018-07-04 15:50:08 +00:00
|
|
|
await manager.async_create_refresh_token(user)
|
|
|
|
|
2018-07-09 16:24:46 +00:00
|
|
|
token = await manager.async_create_refresh_token(user, CLIENT_ID)
|
2018-07-04 15:50:08 +00:00
|
|
|
assert token is not None
|
2018-07-09 16:24:46 +00:00
|
|
|
assert token.client_id == CLIENT_ID
|
2018-07-01 17:36:50 +00:00
|
|
|
|
|
|
|
|
2018-07-04 15:50:08 +00:00
|
|
|
async def test_refresh_token_not_requires_client_for_system_user(hass):
|
|
|
|
"""Test that we can add a system user."""
|
2018-07-01 17:36:50 +00:00
|
|
|
manager = await auth.auth_manager_from_config(hass, [])
|
2018-07-04 15:50:08 +00:00
|
|
|
user = await manager.async_create_system_user('Hass.io')
|
|
|
|
assert user.system_generated is True
|
|
|
|
|
2018-07-01 17:36:50 +00:00
|
|
|
with pytest.raises(ValueError):
|
2018-07-09 16:24:46 +00:00
|
|
|
await manager.async_create_refresh_token(user, CLIENT_ID)
|
2018-07-04 15:50:08 +00:00
|
|
|
|
|
|
|
token = await manager.async_create_refresh_token(user)
|
|
|
|
assert token is not None
|
|
|
|
assert token.client_id is None
|