async def test_load_application_credentials( hass: HomeAssistant, component_setup: ComponentSetup, mock_calendars_yaml: None, mock_calendars_list: ApiResult, test_api_calendar: dict[str, Any], mock_events_list: ApiResult, setup_config_entry: MockConfigEntry, ) -> None: """Test loading an application credentials and a config entry.""" assert await async_setup_component(hass, "application_credentials", {}) await async_import_client_credential( hass, DOMAIN, ClientCredential("client-id", "client-secret"), "device_auth") mock_calendars_list({"items": [test_api_calendar]}) mock_events_list({}) assert await component_setup() state = hass.states.get(TEST_API_ENTITY) assert state assert state.name == TEST_API_ENTITY_NAME assert state.state == STATE_OFF # No yaml config loaded that overwrites the entity name assert not hass.states.get(TEST_YAML_ENTITY)
async def mock_impl(hass): """Mock implementation.""" await async_setup_component(hass, DOMAIN, {}) await hass.async_block_till_done() await async_import_client_credential( hass, DOMAIN, ClientCredential(CLIENT_ID, CLIENT_SECRET), "cred")
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: """Set up the Netatmo component.""" hass.data[DOMAIN] = { DATA_PERSONS: {}, DATA_DEVICE_IDS: {}, DATA_SCHEDULES: {}, DATA_HOMES: {}, DATA_EVENTS: {}, DATA_CAMERAS: {}, } if DOMAIN not in config: return True await async_import_client_credential( hass, DOMAIN, ClientCredential( config[DOMAIN][CONF_CLIENT_ID], config[DOMAIN][CONF_CLIENT_SECRET], ), ) _LOGGER.warning( "Configuration of Netatmo integration in YAML is deprecated and " "will be removed in a future release; Your existing configuration " "(including OAuth Application Credentials) have been imported into " "the UI automatically and can be safely removed from your " "configuration.yaml file" ) return True
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: """Set up the Honeywell Lyric component.""" hass.data[DOMAIN] = {} if DOMAIN not in config: return True await async_import_client_credential( hass, DOMAIN, ClientCredential( config[DOMAIN][CONF_CLIENT_ID], config[DOMAIN][CONF_CLIENT_SECRET], ), ) _LOGGER.warning( "Configuration of Honeywell Lyric integration in YAML is deprecated " "and will be removed in a future release; Your existing OAuth " "Application Credentials have been imported into the UI " "automatically and can be safely removed from your " "configuration.yaml file" ) return True
async def setup_credentials(hass: HomeAssistant) -> None: """Fixture to setup credentials.""" assert await async_setup_component(hass, "application_credentials", {}) await async_import_client_credential( hass, DOMAIN, ClientCredential(CLIENT_ID, CLIENT_SECRET), )
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: """Set up the Google component.""" if DOMAIN not in config: return True conf = config.get(DOMAIN, {}) hass.data[DOMAIN] = {DATA_CONFIG: conf} if CONF_CLIENT_ID in conf and CONF_CLIENT_SECRET in conf: await async_import_client_credential( hass, DOMAIN, ClientCredential( conf[CONF_CLIENT_ID], conf[CONF_CLIENT_SECRET], ), DEVICE_AUTH_IMPL, ) # Import credentials from the old token file into the new way as # a ConfigEntry managed by home assistant. storage = Storage(hass.config.path(TOKEN_FILE)) creds = await hass.async_add_executor_job(storage.get) if creds and get_feature_access(hass).scope in creds.scopes: _LOGGER.debug("Importing configuration entry with credentials") hass.async_create_task( hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_IMPORT}, data={ "creds": creds, }, )) async_create_issue( hass, DOMAIN, "deprecated_yaml", breaks_in_ha_version="2022.9.0", # Warning first added in 2022.6.0 is_fixable=False, severity=IssueSeverity.WARNING, translation_key="deprecated_yaml", ) if conf.get(CONF_TRACK_NEW) is False: # The track_new as False would previously result in new entries # in google_calendars.yaml with track set to False which is # handled at calendar entity creation time. async_create_issue( hass, DOMAIN, "removed_track_new_yaml", breaks_in_ha_version="2022.6.0", is_fixable=False, severity=IssueSeverity.WARNING, translation_key="removed_track_new_yaml", ) return True
async def component_setup(hass: HomeAssistant) -> None: """Fixture for setting up the integration.""" result = await async_setup_component(hass, DOMAIN, {}) await hass.async_block_till_done() await async_import_client_credential(hass, DOMAIN, ClientCredential("client", "secret"), "cred") assert result
async def test_full_flow_application_creds( hass: HomeAssistant, mock_code_flow: Mock, mock_exchange: Mock, config: dict[str, Any], component_setup: ComponentSetup, ) -> None: """Test successful creds setup.""" assert await component_setup() await async_import_client_credential( hass, DOMAIN, ClientCredential(CLIENT_ID, CLIENT_SECRET), "imported-cred") result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER}) assert result.get("type") == "progress" assert result.get("step_id") == "auth" assert "description_placeholders" in result assert "url" in result["description_placeholders"] with patch("homeassistant.components.google.async_setup_entry", return_value=True) as mock_setup: # Run one tick to invoke the credential exchange check now = utcnow() await fire_alarm(hass, now + CODE_CHECK_ALARM_TIMEDELTA) await hass.async_block_till_done() result = await hass.config_entries.flow.async_configure( flow_id=result["flow_id"]) assert result.get("type") == "create_entry" assert result.get("title") == EMAIL_ADDRESS assert "data" in result data = result["data"] assert "token" in data assert 0 < data["token"]["expires_in"] < 8 * 86400 assert (datetime.datetime.now().timestamp() <= data["token"]["expires_at"] < (datetime.datetime.now() + datetime.timedelta(days=8)).timestamp()) data["token"].pop("expires_at") data["token"].pop("expires_in") assert data == { "auth_implementation": "imported-cred", "token": { "access_token": "ACCESS_TOKEN", "refresh_token": "REFRESH_TOKEN", "scope": "https://www.googleapis.com/auth/calendar", "token_type": "Bearer", }, } assert result.get("options") == {"calendar_access": "read_write"} assert len(mock_setup.mock_calls) == 1 entries = hass.config_entries.async_entries(DOMAIN) assert len(entries) == 1
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: """Set up the Google component.""" if DOMAIN not in config: return True conf = config.get(DOMAIN, {}) hass.data[DOMAIN] = {DATA_CONFIG: conf} if CONF_CLIENT_ID in conf and CONF_CLIENT_SECRET in conf: await async_import_client_credential( hass, DOMAIN, ClientCredential( conf[CONF_CLIENT_ID], conf[CONF_CLIENT_SECRET], ), DEVICE_AUTH_IMPL, ) # Import credentials from the old token file into the new way as # a ConfigEntry managed by home assistant. storage = Storage(hass.config.path(TOKEN_FILE)) creds = await hass.async_add_executor_job(storage.get) if creds and get_feature_access(hass).scope in creds.scopes: _LOGGER.debug("Importing configuration entry with credentials") hass.async_create_task( hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_IMPORT}, data={ "creds": creds, }, ) ) _LOGGER.warning( "Configuration of Google Calendar in YAML in configuration.yaml is " "is deprecated and will be removed in a future release; Your existing " "OAuth Application Credentials and access settings have been imported " "into the UI automatically and can be safely removed from your " "configuration.yaml file" ) if conf.get(CONF_TRACK_NEW) is False: # The track_new as False would previously result in new entries # in google_calendars.yaml with track set to Fasle which is # handled at calendar entity creation time. _LOGGER.warning( "You must manually set the integration System Options in the " "UI to disable newly discovered entities going forward" ) return True
async def test_full_flow( hass: HomeAssistant, hass_client_no_auth, aioclient_mock, current_request_with_host, ) -> None: """Check full flow.""" await async_setup_component(hass, DOMAIN, {}) await hass.async_block_till_done() await async_import_client_credential( hass, DOMAIN, ClientCredential(CLIENT_ID, CLIENT_SECRET), "cred") result = await hass.config_entries.flow.async_init( "senz", context={"source": config_entries.SOURCE_USER}) state = config_entry_oauth2_flow._encode_jwt( hass, { "flow_id": result["flow_id"], "redirect_uri": "https://example.com/auth/external/callback", }, ) assert result["url"] == ( f"{AUTHORIZATION_ENDPOINT}?response_type=code&client_id={CLIENT_ID}" "&redirect_uri=https://example.com/auth/external/callback" f"&state={state}&scope=restapi+offline_access") client = await hass_client_no_auth() resp = await client.get(f"/auth/external/callback?code=abcd&state={state}") assert resp.status == 200 assert resp.headers["content-type"] == "text/html; charset=utf-8" aioclient_mock.post( TOKEN_ENDPOINT, json={ "refresh_token": "mock-refresh-token", "access_token": "mock-access-token", "type": "Bearer", "expires_in": 60, }, ) with patch("homeassistant.components.senz.async_setup_entry", return_value=True) as mock_setup: await hass.config_entries.flow.async_configure(result["flow_id"]) assert len(hass.config_entries.async_entries(DOMAIN)) == 1 assert len(mock_setup.mock_calls) == 1
async def test_multiple_config_entries( hass: HomeAssistant, mock_code_flow: Mock, mock_exchange: Mock, config: dict[str, Any], config_entry: MockConfigEntry, component_setup: ComponentSetup, ) -> None: """Test that multiple config entries can be set at once.""" assert await component_setup() await async_import_client_credential( hass, DOMAIN, ClientCredential(CLIENT_ID, CLIENT_SECRET), "imported-cred") # Load a config entry config_entry.add_to_hass(hass) with patch("homeassistant.components.google.async_setup_entry", return_value=True) as mock_setup: await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() assert len(mock_setup.mock_calls) == 1 entries = hass.config_entries.async_entries(DOMAIN) assert len(entries) == 1 # Start a new config flow result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER}) assert result.get("type") == "progress" assert result.get("step_id") == "auth" assert "description_placeholders" in result assert "url" in result["description_placeholders"] with patch("homeassistant.components.google.async_setup_entry", return_value=True) as mock_setup: # Run one tick to invoke the credential exchange check now = utcnow() await fire_alarm(hass, now + CODE_CHECK_ALARM_TIMEDELTA) await hass.async_block_till_done() result = await hass.config_entries.flow.async_configure( flow_id=result["flow_id"]) assert result.get("type") == "create_entry" assert result.get("title") == "*****@*****.**" assert len(mock_setup.mock_calls) == 1 entries = hass.config_entries.async_entries(DOMAIN) assert len(entries) == 2
async def async_import_config(hass: HomeAssistant, entry: ConfigEntry) -> None: """Attempt to import configuration.yaml settings.""" config = hass.data[DOMAIN][DATA_NEST_CONFIG] new_data = { CONF_PROJECT_ID: config[CONF_PROJECT_ID], **entry.data, } if CONF_SUBSCRIBER_ID not in entry.data: if CONF_SUBSCRIBER_ID not in config: raise ValueError("Configuration option 'subscriber_id' missing") new_data.update( { CONF_SUBSCRIBER_ID: config[CONF_SUBSCRIBER_ID], CONF_SUBSCRIBER_ID_IMPORTED: True, # Don't delete user managed subscriber } ) hass.config_entries.async_update_entry( entry, data=new_data, unique_id=new_data[CONF_PROJECT_ID] ) if entry.data["auth_implementation"] == INSTALLED_AUTH_DOMAIN: # App Auth credentials have been deprecated and must be re-created # by the user in the config flow raise ConfigEntryAuthFailed( "Google has deprecated App Auth credentials, and the integration " "must be reconfigured in the UI to restore access to Nest Devices." ) if entry.data["auth_implementation"] == WEB_AUTH_DOMAIN: await async_import_client_credential( hass, DOMAIN, ClientCredential( config[CONF_CLIENT_ID], config[CONF_CLIENT_SECRET], ), WEB_AUTH_DOMAIN, ) _LOGGER.warning( "Configuration of Nest integration in YAML is deprecated and " "will be removed in a future release; Your existing configuration " "(including OAuth Application Credentials) has been imported into " "the UI automatically and can be safely removed from your " "configuration.yaml file" )
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: """Set up the Google component.""" if DOMAIN not in config: return True conf = config.get(DOMAIN, {}) hass.data[DOMAIN] = {DATA_CONFIG: conf} if CONF_CLIENT_ID in conf and CONF_CLIENT_SECRET in conf: await async_import_client_credential( hass, DOMAIN, ClientCredential( conf[CONF_CLIENT_ID], conf[CONF_CLIENT_SECRET], ), DEVICE_AUTH_IMPL, ) # Import credentials from the old token file into the new way as # a ConfigEntry managed by home assistant. storage = Storage(hass.config.path(TOKEN_FILE)) creds = await hass.async_add_executor_job(storage.get) if creds and get_feature_access(hass).scope in creds.scopes: _LOGGER.debug("Importing configuration entry with credentials") hass.async_create_task( hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_IMPORT}, data={ "creds": creds, }, ) ) _LOGGER.warning( "Configuration of Google Calendar in YAML in configuration.yaml is " "is deprecated and will be removed in a future release; Your existing " "OAuth Application Credentials and other settings have been imported " "into the UI automatically and can be safely removed from your " "configuration.yaml file" ) return True
"""Set up the Withings component.""" if not (conf := config.get(DOMAIN)): # Apply the defaults. conf = CONFIG_SCHEMA({DOMAIN: {}})[DOMAIN] hass.data[DOMAIN] = {const.CONFIG: conf} return True hass.data[DOMAIN] = {const.CONFIG: conf} # Setup the oauth2 config flow. if CONF_CLIENT_ID in conf: await async_import_client_credential( hass, DOMAIN, ClientCredential( conf[CONF_CLIENT_ID], conf[CONF_CLIENT_SECRET], ), ) _LOGGER.warning( "Configuration of Withings integration OAuth2 credentials in YAML " "is deprecated and will be removed in a future release; Your " "existing OAuth Application Credentials have been imported into " "the UI automatically and can be safely removed from your " "configuration.yaml file") return True async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up Withings from a config entry.""" config_updates = {}
"subscriber_id": SUBSCRIBER_ID, }, ) TEST_CONFIGFLOW_HYBRID = NestTestConfig(TEST_CONFIG_HYBRID.config) # Exercises mode where all configuration is from the config flow TEST_CONFIG_APP_CREDS = NestTestConfig( config_entry_data={ "sdm": {}, "token": create_token_entry(), "project_id": PROJECT_ID, "cloud_project_id": CLOUD_PROJECT_ID, "subscriber_id": SUBSCRIBER_ID, }, auth_implementation="imported-cred", credential=ClientCredential(CLIENT_ID, CLIENT_SECRET), ) TEST_CONFIGFLOW_APP_CREDS = NestTestConfig( config=TEST_CONFIG_APP_CREDS.config, auth_implementation="imported-cred", credential=ClientCredential(CLIENT_ID, CLIENT_SECRET), ) TEST_CONFIG_LEGACY = NestTestConfig( config={ "nest": { "client_id": "some-client-id", "client_secret": "some-client-secret", }, }, config_entry_data={
CONF_AUTH_DOMAIN, DOMAIN, AuthorizationServer, ClientCredential, async_import_client_credential, ) from homeassistant.const import CONF_CLIENT_ID, CONF_CLIENT_SECRET, CONF_DOMAIN from homeassistant.core import HomeAssistant from homeassistant.helpers import config_entry_oauth2_flow from homeassistant.setup import async_setup_component from tests.common import mock_platform CLIENT_ID = "some-client-id" CLIENT_SECRET = "some-client-secret" DEVELOPER_CREDENTIAL = ClientCredential(CLIENT_ID, CLIENT_SECRET) ID = "fake_integration_some_client_id" AUTHORIZE_URL = "https://example.com/auth" TOKEN_URL = "https://example.com/oauth2/v4/token" REFRESH_TOKEN = "mock-refresh-token" ACCESS_TOKEN = "mock-access-token" TEST_DOMAIN = "fake_integration" @pytest.fixture async def authorization_server() -> AuthorizationServer: """Fixture AuthorizationServer for mock application_credentials integration.""" return AuthorizationServer(AUTHORIZE_URL, TOKEN_URL)
) from homeassistant.const import ( CONF_CLIENT_ID, CONF_CLIENT_SECRET, CONF_DOMAIN, CONF_NAME, ) from homeassistant.core import HomeAssistant from homeassistant.helpers import config_entry_oauth2_flow from homeassistant.setup import async_setup_component from tests.common import mock_platform CLIENT_ID = "some-client-id" CLIENT_SECRET = "some-client-secret" DEVELOPER_CREDENTIAL = ClientCredential(CLIENT_ID, CLIENT_SECRET) NAMED_CREDENTIAL = ClientCredential(CLIENT_ID, CLIENT_SECRET, "Name") ID = "fake_integration_some_client_id" AUTHORIZE_URL = "https://example.com/auth" TOKEN_URL = "https://example.com/oauth2/v4/token" REFRESH_TOKEN = "mock-refresh-token" ACCESS_TOKEN = "mock-access-token" NAME = "Name" TEST_DOMAIN = "fake_integration" @pytest.fixture async def authorization_server() -> AuthorizationServer: """Fixture AuthorizationServer for mock application_credentials integration.""" return AuthorizationServer(AUTHORIZE_URL, TOKEN_URL)
async def setup_credentials(hass: HomeAssistant) -> None: """Fixture to setup credentials.""" assert await async_setup_component(hass, "application_credentials", {}) await async_import_client_credential(hass, DOMAIN, ClientCredential("client", "secret"), "credentials")
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: """Set up Home Connect component.""" hass.data[DOMAIN] = {} if DOMAIN in config: await async_import_client_credential( hass, DOMAIN, ClientCredential( config[DOMAIN][CONF_CLIENT_ID], config[DOMAIN][CONF_CLIENT_SECRET], ), ) _LOGGER.warning( "Configuration of Home Connect integration in YAML is deprecated and " "will be removed in a future release; Your existing OAuth " "Application Credentials have been imported into the UI " "automatically and can be safely removed from your " "configuration.yaml file") async def _async_service_program(call, method): """Execute calls to services taking a program.""" program = call.data[ATTR_PROGRAM] device_id = call.data[ATTR_DEVICE_ID] options = [] option_key = call.data.get(ATTR_KEY) if option_key is not None: option = {ATTR_KEY: option_key, ATTR_VALUE: call.data[ATTR_VALUE]} option_unit = call.data.get(ATTR_UNIT) if option_unit is not None: option[ATTR_UNIT] = option_unit options.append(option) appliance = _get_appliance_by_device_id(hass, device_id) await hass.async_add_executor_job(getattr(appliance, method), program, options) async def _async_service_command(call, command): """Execute calls to services executing a command.""" device_id = call.data[ATTR_DEVICE_ID] appliance = _get_appliance_by_device_id(hass, device_id) await hass.async_add_executor_job(appliance.execute_command, command) async def _async_service_key_value(call, method): """Execute calls to services taking a key and value.""" key = call.data[ATTR_KEY] value = call.data[ATTR_VALUE] unit = call.data.get(ATTR_UNIT) device_id = call.data[ATTR_DEVICE_ID] appliance = _get_appliance_by_device_id(hass, device_id) if unit is not None: await hass.async_add_executor_job( getattr(appliance, method), key, value, unit, ) else: await hass.async_add_executor_job( getattr(appliance, method), key, value, ) async def async_service_option_active(call): """Service for setting an option for an active program.""" await _async_service_key_value(call, "set_options_active_program") async def async_service_option_selected(call): """Service for setting an option for a selected program.""" await _async_service_key_value(call, "set_options_selected_program") async def async_service_setting(call): """Service for changing a setting.""" await _async_service_key_value(call, "set_setting") async def async_service_pause_program(call): """Service for pausing a program.""" await _async_service_command(call, BSH_PAUSE) async def async_service_resume_program(call): """Service for resuming a paused program.""" await _async_service_command(call, BSH_RESUME) async def async_service_select_program(call): """Service for selecting a program.""" await _async_service_program(call, "select_program") async def async_service_start_program(call): """Service for starting a program.""" await _async_service_program(call, "start_program") hass.services.async_register( DOMAIN, SERVICE_OPTION_ACTIVE, async_service_option_active, schema=SERVICE_OPTION_SCHEMA, ) hass.services.async_register( DOMAIN, SERVICE_OPTION_SELECTED, async_service_option_selected, schema=SERVICE_OPTION_SCHEMA, ) hass.services.async_register(DOMAIN, SERVICE_SETTING, async_service_setting, schema=SERVICE_SETTING_SCHEMA) hass.services.async_register( DOMAIN, SERVICE_PAUSE_PROGRAM, async_service_pause_program, schema=SERVICE_COMMAND_SCHEMA, ) hass.services.async_register( DOMAIN, SERVICE_RESUME_PROGRAM, async_service_resume_program, schema=SERVICE_COMMAND_SCHEMA, ) hass.services.async_register( DOMAIN, SERVICE_SELECT_PROGRAM, async_service_select_program, schema=SERVICE_PROGRAM_SCHEMA, ) hass.services.async_register( DOMAIN, SERVICE_START_PROGRAM, async_service_start_program, schema=SERVICE_PROGRAM_SCHEMA, ) return True
async def async_import_config(hass: HomeAssistant, entry: ConfigEntry) -> None: """Attempt to import configuration.yaml settings.""" config = hass.data[DOMAIN][DATA_NEST_CONFIG] new_data = { CONF_PROJECT_ID: config[CONF_PROJECT_ID], **entry.data, } if CONF_SUBSCRIBER_ID not in entry.data: if CONF_SUBSCRIBER_ID not in config: raise ValueError("Configuration option 'subscriber_id' missing") new_data.update({ CONF_SUBSCRIBER_ID: config[CONF_SUBSCRIBER_ID], CONF_SUBSCRIBER_ID_IMPORTED: True, # Don't delete user managed subscriber }) hass.config_entries.async_update_entry(entry, data=new_data, unique_id=new_data[CONF_PROJECT_ID]) if entry.data["auth_implementation"] == INSTALLED_AUTH_DOMAIN: # App Auth credentials have been deprecated and must be re-created # by the user in the config flow async_create_issue( hass, DOMAIN, "removed_app_auth", is_fixable=False, severity=IssueSeverity.ERROR, translation_key="removed_app_auth", translation_placeholders={ "more_info_url": "https://www.home-assistant.io/more-info/nest-auth-deprecation", "documentation_url": "https://www.home-assistant.io/integrations/nest/", }, ) raise ConfigEntryAuthFailed( "Google has deprecated App Auth credentials, and the integration " "must be reconfigured in the UI to restore access to Nest Devices." ) if entry.data["auth_implementation"] == WEB_AUTH_DOMAIN: await async_import_client_credential( hass, DOMAIN, ClientCredential( config[CONF_CLIENT_ID], config[CONF_CLIENT_SECRET], ), WEB_AUTH_DOMAIN, ) async_create_issue( hass, DOMAIN, "deprecated_yaml", breaks_in_ha_version="2022.10.0", is_fixable=False, severity=IssueSeverity.WARNING, translation_key="deprecated_yaml", )