async def test_legacy_login_flow_validates(legacy_data, opp): """Test in legacy mode login flow.""" legacy_data.add_auth("test-user", "test-pass") await legacy_data.async_save() provider = opp_auth.OppAuthProvider(opp, auth_store.AuthStore(opp), {"type": "openpeerpower"}) flow = await provider.async_login_flow({}) result = await flow.async_step_init() assert result["type"] == data_entry_flow.RESULT_TYPE_FORM result = await flow.async_step_init({ "username": "******", "password": "******" }) assert result["type"] == data_entry_flow.RESULT_TYPE_FORM assert result["errors"]["base"] == "invalid_auth" result = await flow.async_step_init({ "username": "******", "password": "******" }) assert result["type"] == data_entry_flow.RESULT_TYPE_FORM assert result["errors"]["base"] == "invalid_auth" result = await flow.async_step_init({ "username": "******", "password": "******" }) assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY assert result["data"]["username"] == "test-user"
async def test_saving_loading(opp, opp_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( opp, [{ "type": "insecure_example", "users": [{ "username": "******", "password": "******" }], }], [], ) step = await manager.login_flow.async_init(("insecure_example", None)) step = await manager.login_flow.async_configure(step["flow_id"], { "username": "******", "password": "******" }) credential = step["result"] user = await manager.async_get_or_create_user(credential) await manager.async_activate_user(user) # the first refresh token will be used to create access token refresh_token = await manager.async_create_refresh_token( user, CLIENT_ID, credential=credential) manager.async_create_access_token(refresh_token, "192.168.0.1") # the second refresh token will not be used await manager.async_create_refresh_token(user, "dummy-client", credential=credential) await flush_store(manager._store._store) store2 = auth_store.AuthStore(opp) users = await store2.async_get_users() assert len(users) == 1 assert users[0].permissions == user.permissions assert users[0] == user assert len(users[0].refresh_tokens) == 2 for r_token in users[0].refresh_tokens.values(): if r_token.client_id == CLIENT_ID: # verify the first refresh token assert r_token.last_used_at is not None assert r_token.last_used_ip == "192.168.0.1" elif r_token.client_id == "dummy-client": # verify the second refresh token assert r_token.last_used_at is None assert r_token.last_used_ip is None else: assert False, "Unknown client_id: %s" % r_token.client_id
async def test_system_groups_store_id_and_name(opp, opp_storage): """Test that for system groups we store the ID and name. Name is stored so that we remain backwards compat with < 0.82. """ store = auth_store.AuthStore(opp) await store._async_load() data = store._data_to_save() assert len(data["users"]) == 0 assert data["groups"] == [ {"id": auth_store.GROUP_ID_ADMIN, "name": auth_store.GROUP_NAME_ADMIN}, {"id": auth_store.GROUP_ID_USER, "name": auth_store.GROUP_NAME_USER}, {"id": auth_store.GROUP_ID_READ_ONLY, "name": auth_store.GROUP_NAME_READ_ONLY}, ]
async def test_loading_race_condition(opp): """Test only one storage load called when concurrent loading occurred .""" store = auth_store.AuthStore(opp) with asynctest.patch( "openpeerpower.helpers.entity_registry.async_get_registry" ) as mock_ent_registry, asynctest.patch( "openpeerpower.helpers.device_registry.async_get_registry" ) as mock_dev_registry, asynctest.patch( "openpeerpower.helpers.storage.Store.async_load" ) as mock_load: results = await asyncio.gather(store.async_get_users(), store.async_get_users()) mock_ent_registry.assert_called_once_with(opp) mock_dev_registry.assert_called_once_with(opp) mock_load.assert_called_once_with() assert results[0] == results[1]
async def test_loading_empty_data(opp, opp_storage): """Test we correctly load with no existing data.""" store = auth_store.AuthStore(opp) groups = await store.async_get_groups() assert len(groups) == 3 admin_group = groups[0] assert admin_group.name == auth_store.GROUP_NAME_ADMIN assert admin_group.system_generated assert admin_group.id == auth_store.GROUP_ID_ADMIN user_group = groups[1] assert user_group.name == auth_store.GROUP_NAME_USER assert user_group.system_generated assert user_group.id == auth_store.GROUP_ID_USER read_group = groups[2] assert read_group.name == auth_store.GROUP_NAME_READ_ONLY assert read_group.system_generated assert read_group.id == auth_store.GROUP_ID_READ_ONLY users = await store.async_get_users() assert len(users) == 0
async def test_race_condition_in_data_loading(opp): """Test race condition in the opp_auth.Data loading. Ref issue: https://github.com/openpeerpower/core/issues/21569 """ counter = 0 async def mock_load(_): """Mock of openpeerpower.helpers.storage.Store.async_load.""" nonlocal counter counter += 1 await asyncio.sleep(0) provider = opp_auth.OppAuthProvider(opp, auth_store.AuthStore(opp), {"type": "openpeerpower"}) with patch("openpeerpower.helpers.storage.Store.async_load", new=mock_load): task1 = provider.async_validate_login("user", "pass") task2 = provider.async_validate_login("user", "pass") results = await asyncio.gather(task1, task2, return_exceptions=True) assert counter == 1 assert isinstance(results[0], opp_auth.InvalidAuth) # results[1] will be a TypeError if race condition occurred assert isinstance(results[1], opp_auth.InvalidAuth)
async def async_test_open_peer_power(loop, load_registries=True): """Return a Open Peer Power object pointing at test config dir.""" opp = ha.OpenPeerPower() store = auth_store.AuthStore(opp) opp.auth = auth.AuthManager(opp, store, {}, {}) ensure_auth_manager_loaded(opp.auth) INSTANCES.append(opp) orig_async_add_job = opp.async_add_job orig_async_add_executor_job = opp.async_add_executor_job orig_async_create_task = opp.async_create_task def async_add_job(target, *args): """Add job.""" check_target = target while isinstance(check_target, ft.partial): check_target = check_target.func if isinstance(check_target, Mock) and not isinstance(target, AsyncMock): fut = asyncio.Future() fut.set_result(target(*args)) return fut return orig_async_add_job(target, *args) def async_add_executor_job(target, *args): """Add executor job.""" check_target = target while isinstance(check_target, ft.partial): check_target = check_target.func if isinstance(check_target, Mock): fut = asyncio.Future() fut.set_result(target(*args)) return fut return orig_async_add_executor_job(target, *args) def async_create_task(coroutine): """Create task.""" if isinstance(coroutine, Mock) and not isinstance(coroutine, AsyncMock): fut = asyncio.Future() fut.set_result(None) return fut return orig_async_create_task(coroutine) async def async_wait_for_task_count(self, max_remaining_tasks: int = 0) -> None: """Block until at most max_remaining_tasks remain. Based on OpenPeerPower.async_block_till_done """ # To flush out any call_soon_threadsafe await asyncio.sleep(0) start_time: float | None = None while len(self._pending_tasks) > max_remaining_tasks: pending: Collection[Awaitable[Any]] = [ task for task in self._pending_tasks if not task.done() ] self._pending_tasks.clear() if len(pending) > max_remaining_tasks: remaining_pending = await self._await_count_and_log_pending( pending, max_remaining_tasks=max_remaining_tasks) self._pending_tasks.extend(remaining_pending) if start_time is None: # Avoid calling monotonic() until we know # we may need to start logging blocked tasks. start_time = 0 elif start_time == 0: # If we have waited twice then we set the start # time start_time = monotonic() elif monotonic() - start_time > BLOCK_LOG_TIMEOUT: # We have waited at least three loops and new tasks # continue to block. At this point we start # logging all waiting tasks. for task in pending: _LOGGER.debug("Waiting for task: %s", task) else: self._pending_tasks.extend(pending) await asyncio.sleep(0) async def _await_count_and_log_pending( self, pending: Collection[Awaitable[Any]], max_remaining_tasks: int = 0) -> Collection[Awaitable[Any]]: """Block at most max_remaining_tasks remain and log tasks that take a long time. Based on OpenPeerPower._await_and_log_pending """ wait_time = 0 return_when = asyncio.ALL_COMPLETED if max_remaining_tasks: return_when = asyncio.FIRST_COMPLETED while len(pending) > max_remaining_tasks: _, pending = await asyncio.wait(pending, timeout=BLOCK_LOG_TIMEOUT, return_when=return_when) if not pending or max_remaining_tasks: return pending wait_time += BLOCK_LOG_TIMEOUT for task in pending: _LOGGER.debug("Waited %s seconds for task: %s", wait_time, task) return [] opp.async_add_job = async_add_job opp.async_add_executor_job = async_add_executor_job opp.async_create_task = async_create_task opp.async_wait_for_task_count = types.MethodType(async_wait_for_task_count, opp) opp._await_count_and_log_pending = types.MethodType( _await_count_and_log_pending, opp) opp.data[loader.DATA_CUSTOM_COMPONENTS] = {} opp.config.location_name = "test home" opp.config.config_dir = get_test_config_dir() opp.config.latitude = 32.87336 opp.config.longitude = -117.22743 opp.config.elevation = 0 opp.config.time_zone = "US/Pacific" opp.config.units = METRIC_SYSTEM opp.config.media_dirs = {"local": get_test_config_dir("media")} opp.config.skip_pip = True opp.config_entries = config_entries.ConfigEntries(opp, {}) opp.config_entries._entries = {} opp.config_entries._store._async_ensure_stop_listener = lambda: None # Load the registries if load_registries: await asyncio.gather( device_registry.async_load(opp), entity_registry.async_load(opp), area_registry.async_load(opp), ) await opp.async_block_till_done() opp.state = ha.CoreState.running # Mock async_start orig_start = opp.async_start async def mock_async_start(): """Start the mocking.""" # We only mock time during tests and we want to track tasks with patch("openpeerpower.core._async_create_timer"), patch.object( opp, "async_stop_track_tasks"): await orig_start() opp.async_start = mock_async_start @ha.callback def clear_instance(event): """Clear global instance.""" INSTANCES.remove(opp) opp.bus.async_listen_once(EVENT_OPENPEERPOWER_CLOSE, clear_instance) return opp
def store(opp): """Mock store.""" return auth_store.AuthStore(opp)
async def test_loading_all_access_group_data_format(opp, opp_storage): """Test we correctly load old data with single group.""" opp_storage[auth_store.STORAGE_KEY] = { "version": 1, "data": { "credentials": [], "users": [ { "id": "user-id", "is_active": True, "is_owner": True, "name": "Paulus", "system_generated": False, "group_ids": ["abcd-all-access"], }, { "id": "system-id", "is_active": True, "is_owner": True, "name": "Opp.io", "system_generated": True, }, ], "groups": [{"id": "abcd-all-access", "name": "All Access"}], "refresh_tokens": [ { "access_token_expiration": 1800.0, "client_id": "http://localhost:8123/", "created_at": "2018-10-03T13:43:19.774637+00:00", "id": "user-token-id", "jwt_key": "some-key", "last_used_at": "2018-10-03T13:43:19.774712+00:00", "token": "some-token", "user_id": "user-id", }, { "access_token_expiration": 1800.0, "client_id": None, "created_at": "2018-10-03T13:43:19.774637+00:00", "id": "system-token-id", "jwt_key": "some-key", "last_used_at": "2018-10-03T13:43:19.774712+00:00", "token": "some-token", "user_id": "system-id", }, { "access_token_expiration": 1800.0, "client_id": "http://localhost:8123/", "created_at": "2018-10-03T13:43:19.774637+00:00", "id": "hidden-because-no-jwt-id", "last_used_at": "2018-10-03T13:43:19.774712+00:00", "token": "some-token", "user_id": "user-id", }, ], }, } store = auth_store.AuthStore(opp) groups = await store.async_get_groups() assert len(groups) == 3 admin_group = groups[0] assert admin_group.name == auth_store.GROUP_NAME_ADMIN assert admin_group.system_generated assert admin_group.id == auth_store.GROUP_ID_ADMIN read_group = groups[1] assert read_group.name == auth_store.GROUP_NAME_READ_ONLY assert read_group.system_generated assert read_group.id == auth_store.GROUP_ID_READ_ONLY user_group = groups[2] assert user_group.name == auth_store.GROUP_NAME_USER assert user_group.system_generated assert user_group.id == auth_store.GROUP_ID_USER users = await store.async_get_users() assert len(users) == 2 owner, system = users assert owner.system_generated is False assert owner.groups == [admin_group] assert len(owner.refresh_tokens) == 1 owner_token = list(owner.refresh_tokens.values())[0] assert owner_token.id == "user-token-id" assert system.system_generated is True assert system.groups == [] assert len(system.refresh_tokens) == 1 system_token = list(system.refresh_tokens.values())[0] assert system_token.id == "system-token-id"