async def test_new_users_admin(mock_opp): """Test newly created users are admin.""" manager = await auth.auth_manager_from_config( mock_opp, [ { "type": "insecure_example", "users": [ { "username": "******", "password": "******", "name": "Test Name", } ], } ], [], ) ensure_auth_manager_loaded(manager) user = await manager.async_create_user("Hello") assert user.is_admin user_cred = await manager.async_get_or_create_user( auth_models.Credentials( id="mock-id", auth_provider_type="insecure_example", auth_provider_id=None, data={"username": "******"}, is_new=True, ) ) assert user_cred.is_admin
async def test_auth_module_expired_session(mock_opp): """Test login as existing user.""" manager = await auth.auth_manager_from_config( mock_opp, [ { "type": "insecure_example", "users": [ { "username": "******", "password": "******", "name": "Test Name", } ], } ], [ { "type": "insecure_example", "data": [{"user_id": "mock-user", "pin": "test-pin"}], } ], ) mock_opp.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( "openpeerpower.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_new_users(mock_opp): """Test newly created users.""" manager = await auth.auth_manager_from_config( mock_opp, [{ "type": "insecure_example", "users": [ { "username": "******", "password": "******", "name": "Test Name", }, { "username": "******", "password": "******", "name": "Test Name", }, { "username": "******", "password": "******", "name": "Test Name", }, ], }], [], ) ensure_auth_manager_loaded(manager) user = await manager.async_create_user("Hello") # first user in the system is owner and admin assert user.is_owner assert user.is_admin assert user.groups == [] user = await manager.async_create_user("Hello 2") assert not user.is_admin assert user.groups == [] user = await manager.async_create_user("Hello 3", ["system-admin"]) assert user.is_admin assert user.groups[0].id == "system-admin" user_cred = await manager.async_get_or_create_user( auth_models.Credentials( id="mock-id", auth_provider_type="insecure_example", auth_provider_id=None, data={"username": "******"}, is_new=True, )) assert user_cred.is_admin
async def test_async_remove_user(opp): """Test removing a user.""" events = [] @callback def user_removed(event): events.append(event) opp.bus.async_listen("user_removed", user_removed) manager = await auth.auth_manager_from_config( opp, [ { "type": "insecure_example", "users": [ { "username": "******", "password": "******", "name": "Test Name", } ], } ], [], ) opp.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 opp.auth.async_remove_user(user) assert len(await manager.async_get_users()) == 0 assert len(user.credentials) == 0 await opp.async_block_till_done() assert len(events) == 1 assert events[0].data["user_id"] == user.id
async def test_match_existing_credentials(store, provider): """See if we match existing users.""" existing = auth_models.Credentials( id=uuid.uuid4(), auth_provider_type="command_line", auth_provider_id=None, data={"username": "******"}, is_new=False, ) provider.async_credentials = Mock(return_value=mock_coro([existing])) credentials = await provider.async_get_or_create_credentials( {"username": "******", "password": "******"} ) assert credentials is existing
async def test_not_raise_exception_when_service_not_exist(opp): """Test login flow will not raise exception when notify service error.""" opp.auth = await auth_manager_from_config( opp, [{ "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(opp.auth) await opp.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 opp.auth.async_enable_user_mfa(user, "notify", {"notify_service": "invalid-notify"}) provider = opp.auth.auth_providers[0] result = await opp.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 opp.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 opp.async_block_till_done()
async def test_match_existing_credentials(store, provider): """See if we match existing users.""" existing = auth_models.Credentials( id=uuid.uuid4(), auth_provider_type="insecure_example", auth_provider_id=None, data={"username": "******"}, is_new=False, ) provider.async_credentials = AsyncMock(return_value=[existing]) credentials = await provider.async_get_or_create_credentials({ "username": "******", "password": "******" }) assert credentials is existing
async def test_refresh_token_provider_validation(mock_opp): """Test that creating access token from refresh token checks with provider.""" manager = await auth.auth_manager_from_config( mock_opp, [{ "type": "insecure_example", "users": [{ "username": "******", "password": "******" }], }], [], ) credential = auth_models.Credentials( id="mock-credential-id", auth_provider_type="insecure_example", auth_provider_id=None, data={"username": "******"}, is_new=False, ) user = MockUser().add_to_auth_manager(manager) user.credentials.append(credential) refresh_token = await manager.async_create_refresh_token( user, CLIENT_ID, credential=credential) ip = "127.0.0.1" assert manager.async_create_access_token(refresh_token, ip) is not None with patch( "openpeerpower.auth.providers.insecure_example.ExampleAuthProvider.async_validate_refresh_token", side_effect=InvalidAuthError("Invalid access"), ) as call, pytest.raises(InvalidAuthError): manager.async_create_access_token(refresh_token, ip) call.assert_called_with(refresh_token, ip)
async def test_login_flow_validates_mfa(opp): """Test login flow with mfa enabled.""" opp.auth = await auth_manager_from_config( opp, [{ "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(opp.auth) await opp.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, ), ) notify_calls = async_mock_service(opp, "notify", "test-notify", NOTIFY_SERVICE_SCHEMA) await opp.auth.async_enable_user_mfa(user, "notify", {"notify_service": "test-notify"}) provider = opp.auth.auth_providers[0] result = await opp.auth.login_flow.async_init((provider.type, provider.id)) assert result["type"] == data_entry_flow.RESULT_TYPE_FORM result = await opp.auth.login_flow.async_configure(result["flow_id"], { "username": "******", "password": "******" }) assert result["type"] == data_entry_flow.RESULT_TYPE_FORM assert result["errors"]["base"] == "invalid_auth" result = await opp.auth.login_flow.async_configure( result["flow_id"], { "username": "******", "password": "******" }) assert result["type"] == data_entry_flow.RESULT_TYPE_FORM assert result["errors"]["base"] == "invalid_auth" with patch("pyotp.HOTP.at", return_value=MOCK_CODE): result = await opp.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 opp.async_block_till_done() assert len(notify_calls) == 1 notify_call = notify_calls[0] assert notify_call.domain == "notify" assert notify_call.service == "test-notify" message = notify_call.data["message"] message.opp = opp assert MOCK_CODE in message.async_render() with patch("pyotp.HOTP.verify", return_value=False): result = await opp.auth.login_flow.async_configure( result["flow_id"], {"code": "invalid-code"}) assert result["type"] == data_entry_flow.RESULT_TYPE_FORM assert result["step_id"] == "mfa" assert result["errors"]["base"] == "invalid_code" # wait service call finished await opp.async_block_till_done() # would not send new code, allow user retry assert len(notify_calls) == 1 # retry twice with patch("pyotp.HOTP.verify", return_value=False), patch("pyotp.HOTP.at", return_value=MOCK_CODE_2): result = await opp.auth.login_flow.async_configure( result["flow_id"], {"code": "invalid-code"}) assert result["type"] == data_entry_flow.RESULT_TYPE_FORM assert result["step_id"] == "mfa" assert result["errors"]["base"] == "invalid_code" # after the 3rd failure, flow abort result = await opp.auth.login_flow.async_configure( result["flow_id"], {"code": "invalid-code"}) assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT assert result["reason"] == "too_many_retry" # wait service call finished await opp.async_block_till_done() # restart login result = await opp.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 opp.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 opp.async_block_till_done() assert len(notify_calls) == 2 notify_call = notify_calls[1] assert notify_call.domain == "notify" assert notify_call.service == "test-notify" message = notify_call.data["message"] message.opp = opp assert MOCK_CODE in message.async_render() with patch("pyotp.HOTP.verify", return_value=True): result = await opp.auth.login_flow.async_configure( result["flow_id"], {"code": MOCK_CODE}) assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY assert result["data"].id == "mock-user"
async def test_login_with_multi_auth_module(mock_opp): """Test login as existing user with multiple auth modules.""" manager = await auth.auth_manager_from_config( mock_opp, [{ "type": "insecure_example", "users": [{ "username": "******", "password": "******", "name": "Test Name", }], }], [ { "type": "insecure_example", "data": [{ "user_id": "mock-user", "pin": "test-pin" }], }, { "type": "insecure_example", "id": "module2", "data": [{ "user_id": "mock-user", "pin": "test-pin2" }], }, ], ) mock_opp.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": "******" }) # After auth_provider validated, request select auth module assert step["type"] == data_entry_flow.RESULT_TYPE_FORM assert step["step_id"] == "select_mfa_module" step = await manager.login_flow.async_configure( step["flow_id"], {"multi_factor_auth_module": "module2"}) assert step["type"] == data_entry_flow.RESULT_TYPE_FORM assert step["step_id"] == "mfa" step = await manager.login_flow.async_configure(step["flow_id"], {"pin": "test-pin2"}) # Finally passed, get credential assert step["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY assert step["result"] assert step["result"].id == "mock-id"
async def test_login_as_existing_user(mock_opp): """Test login as existing user.""" manager = await auth.auth_manager_from_config( mock_opp, [{ "type": "insecure_example", "users": [{ "username": "******", "password": "******", "name": "Test Name", }], }], [], ) mock_opp.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 credential = step["result"] user = await manager.async_get_user_by_credentials(credential) 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_login_flow_validates_mfa(opp): """Test login flow with mfa enabled.""" opp.auth = await auth_manager_from_config( opp, [{ "type": "insecure_example", "users": [{ "username": "******", "password": "******" }], }], [{ "type": "totp" }], ) user = MockUser(id="mock-user", is_owner=False, is_active=False, name="Paulus").add_to_auth_manager(opp.auth) await opp.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 opp.auth.async_enable_user_mfa(user, "totp", {}) provider = opp.auth.auth_providers[0] result = await opp.auth.login_flow.async_init((provider.type, provider.id)) assert result["type"] == data_entry_flow.RESULT_TYPE_FORM result = await opp.auth.login_flow.async_configure(result["flow_id"], { "username": "******", "password": "******" }) assert result["type"] == data_entry_flow.RESULT_TYPE_FORM assert result["errors"]["base"] == "invalid_auth" result = await opp.auth.login_flow.async_configure( result["flow_id"], { "username": "******", "password": "******" }) assert result["type"] == data_entry_flow.RESULT_TYPE_FORM assert result["errors"]["base"] == "invalid_auth" result = await opp.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 with patch("pyotp.TOTP.verify", return_value=False): result = await opp.auth.login_flow.async_configure( result["flow_id"], {"code": "invalid-code"}) assert result["type"] == data_entry_flow.RESULT_TYPE_FORM assert result["step_id"] == "mfa" assert result["errors"]["base"] == "invalid_code" with patch("pyotp.TOTP.verify", return_value=True): result = await opp.auth.login_flow.async_configure( result["flow_id"], {"code": MOCK_CODE}) assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY assert result["data"].id == "mock-user"
async def test_list(opp, opp_ws_client, opp_admin_user): """Test get users.""" group = MockGroup().add_to_opp(opp) owner = MockUser(id="abc", name="Test Owner", is_owner=True, groups=[group]).add_to_opp(opp) owner.credentials.append( auth_models.Credentials( auth_provider_type="openpeerpower", auth_provider_id=None, data={"username": "******"}, )) system = MockUser(id="efg", name="Test Opp.io", system_generated=True).add_to_opp(opp) inactive = MockUser(id="hij", name="Inactive User", is_active=False, groups=[group]).add_to_opp(opp) refresh_token = await opp.auth.async_create_refresh_token( owner, CLIENT_ID, credential=owner.credentials[0]) access_token = opp.auth.async_create_access_token(refresh_token) client = await opp_ws_client(opp, access_token) await client.send_json({"id": 5, "type": auth_config.WS_TYPE_LIST}) result = await client.receive_json() assert result["success"], result data = result["result"] assert len(data) == 4 assert data[0] == { "id": opp_admin_user.id, "username": "******", "name": "Mock User", "is_owner": False, "is_active": True, "system_generated": False, "group_ids": [group.id for group in opp_admin_user.groups], "credentials": [{ "type": "openpeerpower" }], } assert data[1] == { "id": owner.id, "username": "******", "name": "Test Owner", "is_owner": True, "is_active": True, "system_generated": False, "group_ids": [group.id for group in owner.groups], "credentials": [{ "type": "openpeerpower" }], } assert data[2] == { "id": system.id, "username": None, "name": "Test Opp.io", "is_owner": False, "is_active": True, "system_generated": True, "group_ids": [], "credentials": [], } assert data[3] == { "id": inactive.id, "username": None, "name": "Inactive User", "is_owner": False, "is_active": False, "system_generated": False, "group_ids": [group.id for group in inactive.groups], "credentials": [], }
async def test_login_with_auth_module(mock_opp): """Test login as existing user with auth module.""" manager = await auth.auth_manager_from_config( mock_opp, [ { "type": "insecure_example", "users": [ { "username": "******", "password": "******", "name": "Test Name", } ], } ], [ { "type": "insecure_example", "data": [{"user_id": "mock-user", "pin": "test-pin"}], } ], ) mock_opp.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": "******"} ) # After auth_provider validated, request auth module input form assert step["type"] == data_entry_flow.RESULT_TYPE_FORM assert step["step_id"] == "mfa" step = await manager.login_flow.async_configure( step["flow_id"], {"pin": "invalid-pin"} ) # Invalid code error assert step["type"] == data_entry_flow.RESULT_TYPE_FORM assert step["step_id"] == "mfa" assert step["errors"] == {"base": "invalid_code"} step = await manager.login_flow.async_configure( step["flow_id"], {"pin": "test-pin"} ) # Finally passed, get user 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"