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': '******', 'password': '******', 'name': 'Test Name' }] }], []) mock_hass.auth = manager ensure_auth_manager_loaded(manager) # 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) user.credentials.append(auth_models.Credentials( id='mock-id2', auth_provider_type='insecure_example', auth_provider_id=None, data={'username': '******'}, is_new=False, )) # 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) user.credentials.append(auth_models.Credentials( id='mock-id', auth_provider_type='insecure_example', auth_provider_id=None, data={'username': '******'}, 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': '******', 'password': '******', }) assert step['type'] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY user = step['result'] 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'
async def test_create_auth_duplicate_username(hass, hass_ws_client, hass_access_token, hass_storage): """Test we can't create auth with a duplicate username.""" client = await hass_ws_client(hass, hass_access_token) user = MockUser().add_to_hass(hass) hass_storage[prov_ha.STORAGE_KEY] = { 'version': 1, 'data': { 'users': [{ 'username': '******' }] } } await client.send_json({ 'id': 5, 'type': auth_ha.WS_TYPE_CREATE, 'user_id': user.id, 'username': '******', 'password': '******', }) result = await client.receive_json() assert not result['success'], result assert result['error']['code'] == 'username_exists'
async def test_delete_removes_credential(hass, hass_ws_client, hass_access_token, hass_storage): """Test deleting auth that is connected to a user.""" client = await hass_ws_client(hass, hass_access_token) user = MockUser().add_to_hass(hass) hass_storage[prov_ha.STORAGE_KEY] = { 'version': 1, 'data': { 'users': [{ 'username': '******' }] } } user.credentials.append( await hass.auth.auth_providers[0].async_get_or_create_credentials({ 'username': '******'})) await client.send_json({ 'id': 5, 'type': auth_ha.WS_TYPE_DELETE, 'username': '******', }) result = await client.receive_json() assert result['success'], result assert len(hass_storage[prov_ha.STORAGE_KEY]['data']['users']) == 0
async def test_create_auth(hass, hass_ws_client, hass_storage): """Test create auth command works.""" client = await hass_ws_client(hass) user = MockUser().add_to_hass(hass) assert len(user.credentials) == 0 await client.send_json({ "id": 5, "type": "config/auth_provider/homeassistant/create", "user_id": user.id, "username": "******", "password": "******", }) result = await client.receive_json() assert result["success"], result assert len(user.credentials) == 1 creds = user.credentials[0] assert creds.auth_provider_type == "homeassistant" assert creds.auth_provider_id is None assert creds.data == {"username": "******"} assert prov_ha.STORAGE_KEY in hass_storage entry = hass_storage[prov_ha.STORAGE_KEY]["data"]["users"][1] assert entry["username"] == "test-user2"
async def test_delete_removes_credential(hass, hass_ws_client, hass_storage): """Test deleting auth that is connected to a user.""" client = await hass_ws_client(hass) user = MockUser().add_to_hass(hass) hass_storage[prov_ha.STORAGE_KEY] = { "version": 1, "data": { "users": [{ "username": "******" }] }, } user.credentials.append( await hass.auth.auth_providers[0].async_get_or_create_credentials( {"username": "******"})) await client.send_json({ "id": 5, "type": "config/auth_provider/homeassistant/delete", "username": "******", }) result = await client.receive_json() assert result["success"], result assert len(hass_storage[prov_ha.STORAGE_KEY]["data"]["users"]) == 0
async def test_create_auth_duplicate_username(hass, hass_ws_client, hass_storage): """Test we can't create auth with a duplicate username.""" client = await hass_ws_client(hass) user = MockUser().add_to_hass(hass) hass_storage[prov_ha.STORAGE_KEY] = { "version": 1, "data": { "users": [{ "username": "******" }] }, } await client.send_json({ "id": 5, "type": "config/auth_provider/homeassistant/create", "user_id": user.id, "username": "******", "password": "******", }) result = await client.receive_json() assert not result["success"], result assert result["error"]["code"] == "username_exists"
async def test_create_auth(hass, hass_ws_client, hass_access_token, hass_storage): """Test create auth command works.""" client = await hass_ws_client(hass, hass_access_token) user = MockUser().add_to_hass(hass) assert len(user.credentials) == 0 await client.send_json({ 'id': 5, 'type': auth_ha.WS_TYPE_CREATE, 'user_id': user.id, 'username': '******', 'password': '******', }) result = await client.receive_json() assert result['success'], result assert len(user.credentials) == 1 creds = user.credentials[0] assert creds.auth_provider_type == 'homeassistant' assert creds.auth_provider_id is None assert creds.data == { 'username': '******' } assert prov_ha.STORAGE_KEY in hass_storage entry = hass_storage[prov_ha.STORAGE_KEY]['data']['users'][0] assert entry['username'] == 'test-user'
async def test_auth_module_expired_session(mock_hass): """Test login as existing user.""" manager = await auth.auth_manager_from_config( mock_hass, [ { "type": "insecure_example", "users": [ { "username": "******", "password": "******", "name": "Test Name", } ], } ], [ { "type": "insecure_example", "data": [{"user_id": "mock-user", "pin": "test-pin"}], } ], ) mock_hass.auth = manager ensure_auth_manager_loaded(manager) # 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) user.credentials.append( auth_models.Credentials( id="mock-id", auth_provider_type="insecure_example", auth_provider_id=None, data={"username": "******"}, 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": "******", "password": "******"} ) assert step["type"] == data_entry_flow.RESULT_TYPE_FORM assert step["step_id"] == "mfa" with patch( "homeassistant.util.dt.utcnow", return_value=dt_util.utcnow() + MFA_SESSION_EXPIRATION, ): step = await manager.login_flow.async_configure( step["flow_id"], {"pin": "test-pin"} ) # login flow abort due session timeout assert step["type"] == data_entry_flow.RESULT_TYPE_ABORT assert step["reason"] == "login_expired"
async def test_cannot_deactive_owner(mock_hass): """Test that we cannot deactivate the owner.""" manager = await auth.auth_manager_from_config(mock_hass, [], []) owner = MockUser(is_owner=True).add_to_auth_manager(manager) with pytest.raises(ValueError): await manager.async_deactivate_user(owner)
def hass_supervisor_user(hass, local_auth): """Return the Home Assistant Supervisor user.""" admin_group = hass.loop.run_until_complete( hass.auth.async_get_group(GROUP_ID_ADMIN)) return MockUser(name=HASSIO_USER_NAME, groups=[admin_group], system_generated=True).add_to_hass(hass)
async def test_create_auth(opp, opp_ws_client, opp_access_token, opp_storage): """Test create auth command works.""" client = await opp_ws_client(opp, opp_access_token) user = MockUser().add_to_opp(opp) assert len(user.credentials) == 0 await client.send_json( { "id": 5, "type": auth_ha.WS_TYPE_CREATE, "user_id": user.id, "username": "******", "password": "******", } ) result = await client.receive_json() assert result["success"], result assert len(user.credentials) == 1 creds = user.credentials[0] assert creds.auth_provider_type == "openpeerpower" assert creds.auth_provider_id is None assert creds.data == {"username": "******"} assert prov_ha.STORAGE_KEY in opp_storage entry = opp_storage[prov_ha.STORAGE_KEY]["data"]["users"][0] assert entry["username"] == "test-user"
async def test_delete_removes_credential( opp, opp_ws_client, opp_access_token, opp_storage ): """Test deleting auth that is connected to a user.""" client = await opp_ws_client(opp, opp_access_token) user = MockUser().add_to_opp(opp) opp_storage[prov_ha.STORAGE_KEY] = { "version": 1, "data": {"users": [{"username": "******"}]}, } user.credentials.append( await opp.auth.auth_providers[0].async_get_or_create_credentials( {"username": "******"} ) ) await client.send_json( {"id": 5, "type": auth_ha.WS_TYPE_DELETE, "username": "******"} ) result = await client.receive_json() assert result["success"], result assert len(opp_storage[prov_ha.STORAGE_KEY]["data"]["users"]) == 0
async def test_create_auth_duplicate_username( opp, opp_ws_client, opp_access_token, opp_storage ): """Test we can't create auth with a duplicate username.""" client = await opp_ws_client(opp, opp_access_token) user = MockUser().add_to_opp(opp) opp_storage[prov_ha.STORAGE_KEY] = { "version": 1, "data": {"users": [{"username": "******"}]}, } await client.send_json( { "id": 5, "type": auth_ha.WS_TYPE_CREATE, "user_id": user.id, "username": "******", "password": "******", } ) result = await client.receive_json() assert not result["success"], result assert result["error"]["code"] == "username_exists"
async def test_cannot_create_refresh_token_with_invalide_user(hass): """Test that we cannot create refresh token with invalid client id.""" manager = await auth.auth_manager_from_config(hass, []) client = await manager.async_create_client('test') user = MockUser(id='invalid-user') with pytest.raises(ValueError): await manager.async_create_refresh_token(user, client.id)
async def test_exception_handling(): """Test handling of exceptions.""" send_messages = [] user = MockUser() refresh_token = Mock() conn = websocket_api.ActiveConnection( logging.getLogger(__name__), None, send_messages.append, user, refresh_token ) for (exc, code, err) in ( (exceptions.Unauthorized(), websocket_api.ERR_UNAUTHORIZED, "Unauthorized"), ( vol.Invalid("Invalid something"), websocket_api.ERR_INVALID_FORMAT, "Invalid something. Got {'id': 5}", ), (asyncio.TimeoutError(), websocket_api.ERR_TIMEOUT, "Timeout"), ( exceptions.HomeAssistantError("Failed to do X"), websocket_api.ERR_UNKNOWN_ERROR, "Failed to do X", ), (ValueError("Really bad"), websocket_api.ERR_UNKNOWN_ERROR, "Unknown error"), ( exceptions.HomeAssistantError(), websocket_api.ERR_UNKNOWN_ERROR, "Unknown error", ), ): send_messages.clear() conn.async_handle_exception({"id": 5}, exc) assert len(send_messages) == 1 assert send_messages[0]["error"]["code"] == code assert send_messages[0]["error"]["message"] == err
def test_auth_code_store_requires_credentials(mock_credential): """Test we require credentials.""" store, _retrieve = auth._create_auth_code_store() with pytest.raises(ValueError): store(None, MockUser()) store(None, mock_credential)
async def test_auth_module_expired_session(mock_hass): """Test login as existing user.""" manager = await auth.auth_manager_from_config(mock_hass, [{ 'type': 'insecure_example', 'users': [{ 'username': '******', 'password': '******', 'name': 'Test Name' }], }], [{ 'type': 'insecure_example', 'data': [{ 'user_id': 'mock-user', 'pin': 'test-pin' }] }]) mock_hass.auth = manager ensure_auth_manager_loaded(manager) # 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) user.credentials.append( auth_models.Credentials( id='mock-id', auth_provider_type='insecure_example', auth_provider_id=None, data={'username': '******'}, 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': '******', 'password': '******', }) assert step['type'] == data_entry_flow.RESULT_TYPE_FORM assert step['step_id'] == 'mfa' with patch('homeassistant.util.dt.utcnow', return_value=dt_util.utcnow() + SESSION_EXPIRATION): step = await manager.login_flow.async_configure( step['flow_id'], { 'pin': 'test-pin', }) # login flow abort due session timeout assert step['type'] == data_entry_flow.RESULT_TYPE_ABORT assert step['reason'] == 'login_expired'
async def test_refresh_token_with_specific_access_token_expiration(hass): """Test create a refresh token with specific access token expiration.""" manager = await auth.auth_manager_from_config(hass, [], []) user = MockUser().add_to_auth_manager(manager) token = await manager.async_create_refresh_token( user, CLIENT_ID, access_token_expiration=timedelta(days=100)) assert token is not None assert token.client_id == CLIENT_ID assert token.access_token_expiration == timedelta(days=100)
async def test_cannot_create_refresh_token_with_invalide_client_id(hass): """Test that we cannot create refresh token with invalid client id.""" manager = await auth.auth_manager_from_config(hass, []) user = MockUser( id='mock-user', is_owner=False, is_active=False, name='Paulus', ).add_to_auth_manager(manager) with pytest.raises(ValueError): await manager.async_create_refresh_token(user, 'bla')
async def test_remove_refresh_token(mock_hass): """Test that we can remove a refresh token.""" manager = await auth.auth_manager_from_config(mock_hass, [], []) user = MockUser().add_to_auth_manager(manager) refresh_token = await manager.async_create_refresh_token(user, CLIENT_ID) access_token = manager.async_create_access_token(refresh_token) await manager.async_remove_refresh_token(refresh_token) assert await manager.async_get_refresh_token(refresh_token.id) is None assert await manager.async_validate_access_token(access_token) is None
def hass_access_token(hass): """Return an access token to access Home Assistant.""" user = MockUser().add_to_hass(hass) client = hass.loop.run_until_complete( hass.auth.async_create_client( 'Access Token Fixture', redirect_uris=['/'], no_secret=True, )) refresh_token = hass.loop.run_until_complete( hass.auth.async_create_refresh_token(user, client.id)) yield hass.auth.async_create_access_token(refresh_token)
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 with pytest.raises(ValueError): await manager.async_create_refresh_token(user) token = await manager.async_create_refresh_token(user, CLIENT_ID) assert token is not None assert token.client_id == CLIENT_ID
async def test_async_remove_user(hass): """Test removing a user.""" events = [] @callback def user_removed(event): events.append(event) hass.bus.async_listen("user_removed", user_removed) manager = await auth.auth_manager_from_config( hass, [ { "type": "insecure_example", "users": [ { "username": "******", "password": "******", "name": "Test Name", } ], } ], [], ) hass.auth = manager ensure_auth_manager_loaded(manager) # 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) user.credentials.append( auth_models.Credentials( id="mock-id", auth_provider_type="insecure_example", auth_provider_id=None, data={"username": "******"}, is_new=False, ) ) assert len(user.credentials) == 1 await hass.auth.async_remove_user(user) assert len(await manager.async_get_users()) == 0 assert len(user.credentials) == 0 await hass.async_block_till_done() assert len(events) == 1 assert events[0].data["user_id"] == user.id
async def test_not_raise_exception_when_service_not_exist(hass): """Test login flow will not raise exception when notify service error.""" hass.auth = await auth_manager_from_config( hass, [{ 'type': 'insecure_example', 'users': [{ 'username': '******', 'password': '******' }], }], [{ 'type': 'notify', }]) user = MockUser( id='mock-user', is_owner=False, is_active=False, name='Paulus', ).add_to_auth_manager(hass.auth) await hass.auth.async_link_user( user, auth_models.Credentials( id='mock-id', auth_provider_type='insecure_example', auth_provider_id=None, data={'username': '******'}, is_new=False, )) await hass.auth.async_enable_user_mfa(user, 'notify', { 'notify_service': 'invalid-notify', }) provider = hass.auth.auth_providers[0] result = await hass.auth.login_flow.async_init( (provider.type, provider.id)) assert result['type'] == data_entry_flow.RESULT_TYPE_FORM with patch('pyotp.HOTP.at', return_value=MOCK_CODE): result = await hass.auth.login_flow.async_configure( result['flow_id'], { 'username': '******', 'password': '******', }) assert result['type'] == data_entry_flow.RESULT_TYPE_FORM assert result['step_id'] == 'mfa' assert result['data_schema'].schema.get('code') == str # wait service call finished await hass.async_block_till_done()
async def test_delete(opp, opp_ws_client, opp_access_token): """Test delete command works.""" client = await opp_ws_client(opp, opp_access_token) test_user = MockUser(id="efg").add_to_opp(opp) assert len(await opp.auth.async_get_users()) == 2 await client.send_json( {"id": 5, "type": auth_config.WS_TYPE_DELETE, "user_id": test_user.id} ) result = await client.receive_json() assert result["success"], result assert len(await opp.auth.async_get_users()) == 1
async def test_create_access_token(mock_hass): """Test normal refresh_token's jwt_key keep same after used.""" manager = await auth.auth_manager_from_config(mock_hass, [], []) user = MockUser().add_to_auth_manager(manager) refresh_token = await manager.async_create_refresh_token(user, CLIENT_ID) assert refresh_token.token_type == auth_models.TOKEN_TYPE_NORMAL jwt_key = refresh_token.jwt_key access_token = manager.async_create_access_token(refresh_token) assert access_token is not None assert refresh_token.jwt_key == jwt_key jwt_payload = jwt.decode(access_token, jwt_key, algorithm=["HS256"]) assert jwt_payload["iss"] == refresh_token.id assert (jwt_payload["exp"] - jwt_payload["iat"] == timedelta(minutes=30).total_seconds())
async def test_one_long_lived_access_token_per_refresh_token(mock_hass): """Test one refresh_token can only have one long-lived access token.""" manager = await auth.auth_manager_from_config(mock_hass, [], []) user = MockUser().add_to_auth_manager(manager) refresh_token = await manager.async_create_refresh_token( user, client_name="GPS Logger", token_type=auth_models.TOKEN_TYPE_LONG_LIVED_ACCESS_TOKEN, access_token_expiration=timedelta(days=3000), ) assert refresh_token.token_type == auth_models.TOKEN_TYPE_LONG_LIVED_ACCESS_TOKEN access_token = manager.async_create_access_token(refresh_token) jwt_key = refresh_token.jwt_key rt = await manager.async_validate_access_token(access_token) assert rt.id == refresh_token.id with pytest.raises(ValueError): await manager.async_create_refresh_token( user, client_name="GPS Logger", token_type=auth_models.TOKEN_TYPE_LONG_LIVED_ACCESS_TOKEN, access_token_expiration=timedelta(days=3000), ) await manager.async_remove_refresh_token(refresh_token) assert refresh_token.id not in user.refresh_tokens rt = await manager.async_validate_access_token(access_token) assert rt is None, "Previous issued access token has been invoked" refresh_token_2 = await manager.async_create_refresh_token( user, client_name="GPS Logger", token_type=auth_models.TOKEN_TYPE_LONG_LIVED_ACCESS_TOKEN, access_token_expiration=timedelta(days=3000), ) assert refresh_token_2.id != refresh_token.id assert refresh_token_2.token_type == auth_models.TOKEN_TYPE_LONG_LIVED_ACCESS_TOKEN access_token_2 = manager.async_create_access_token(refresh_token_2) jwt_key_2 = refresh_token_2.jwt_key assert access_token != access_token_2 assert jwt_key != jwt_key_2 rt = await manager.async_validate_access_token(access_token_2) jwt_payload = jwt.decode(access_token_2, rt.jwt_key, algorithm=["HS256"]) assert jwt_payload["iss"] == refresh_token_2.id assert ( jwt_payload["exp"] - jwt_payload["iat"] == timedelta(days=3000).total_seconds() )
async def test_refresh_token_type(hass): """Test create a refresh token with token type.""" manager = await auth.auth_manager_from_config(hass, [], []) user = MockUser().add_to_auth_manager(manager) with pytest.raises(ValueError): await manager.async_create_refresh_token( user, CLIENT_ID, token_type=auth_models.TOKEN_TYPE_SYSTEM) token = await manager.async_create_refresh_token( user, CLIENT_ID, token_type=auth_models.TOKEN_TYPE_NORMAL) assert token is not None assert token.client_id == CLIENT_ID assert token.token_type == auth_models.TOKEN_TYPE_NORMAL
async def test_not_raise_exception_when_service_not_exist(hass): """Test login flow will not raise exception when notify service error.""" hass.auth = await auth_manager_from_config( hass, [{ "type": "insecure_example", "users": [{ "username": "******", "password": "******" }], }], [{ "type": "notify" }], ) user = MockUser(id="mock-user", is_owner=False, is_active=False, name="Paulus").add_to_auth_manager(hass.auth) await hass.auth.async_link_user( user, auth_models.Credentials( id="mock-id", auth_provider_type="insecure_example", auth_provider_id=None, data={"username": "******"}, is_new=False, ), ) await hass.auth.async_enable_user_mfa(user, "notify", {"notify_service": "invalid-notify"}) provider = hass.auth.auth_providers[0] result = await hass.auth.login_flow.async_init( (provider.type, provider.id)) assert result["type"] == data_entry_flow.RESULT_TYPE_FORM with patch("pyotp.HOTP.at", return_value=MOCK_CODE): result = await hass.auth.login_flow.async_configure( result["flow_id"], { "username": "******", "password": "******" }) assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT assert result["reason"] == "unknown_error" # wait service call finished await hass.async_block_till_done()
async def test_refresh_token_requires_client_for_user(hass): """Test create refresh token for a user with client_id.""" manager = await auth.auth_manager_from_config(hass, [], []) user = MockUser().add_to_auth_manager(manager) assert user.system_generated is False with pytest.raises(ValueError): await manager.async_create_refresh_token(user) token = await manager.async_create_refresh_token(user, CLIENT_ID) assert token is not None assert token.client_id == CLIENT_ID assert token.token_type == auth_models.TOKEN_TYPE_NORMAL # default access token expiration assert token.access_token_expiration == auth_const.ACCESS_TOKEN_EXPIRATION