def _get_internal_url( opp: OpenPeerPower, *, allow_ip: bool = True, require_current_request: bool = False, require_ssl: bool = False, require_standard_port: bool = False, ) -> str: """Get internal URL of this instance.""" if opp.config.internal_url: internal_url = yarl.URL(opp.config.internal_url) if ( (not require_current_request or internal_url.host == _get_request_host()) and (not require_ssl or internal_url.scheme == "https") and (not require_standard_port or internal_url.is_default_port()) and (allow_ip or not is_ip_address(str(internal_url.host))) ): return normalize_url(str(internal_url)) # Fallback to detected local IP if allow_ip and not ( require_ssl or opp.config.api is None or opp.config.api.use_ssl ): ip_url = yarl.URL.build( scheme="http", host=opp.config.api.local_ip, port=opp.config.api.port ) if ( not is_loopback(ip_address(ip_url.host)) and (not require_current_request or ip_url.host == _get_request_host()) and (not require_standard_port or ip_url.is_default_port()) ): return normalize_url(str(ip_url)) raise NoURLAvailableError
def test_is_ip_address(): """Test if strings are IP addresses.""" assert network_util.is_ip_address("192.168.0.1") assert network_util.is_ip_address("8.8.8.8") assert network_util.is_ip_address("::ffff:127.0.0.0") assert not network_util.is_ip_address("192.168.0.999") assert not network_util.is_ip_address("192.168.0.0/24") assert not network_util.is_ip_address("example.com")
def _get_external_url( opp: OpenPeerPower, *, allow_cloud: bool = True, allow_ip: bool = True, prefer_cloud: bool = False, require_current_request: bool = False, require_ssl: bool = False, require_standard_port: bool = False, ) -> str: """Get external URL of this instance.""" if prefer_cloud and allow_cloud: with suppress(NoURLAvailableError): return _get_cloud_url(opp) if opp.config.external_url: external_url = yarl.URL(opp.config.external_url) if ( (allow_ip or not is_ip_address(str(external_url.host))) and ( not require_current_request or external_url.host == _get_request_host() ) and (not require_standard_port or external_url.is_default_port()) and ( not require_ssl or ( external_url.scheme == "https" and not is_ip_address(str(external_url.host)) ) ) ): return normalize_url(str(external_url)) if allow_cloud: with suppress(NoURLAvailableError): return _get_cloud_url(opp, require_current_request=require_current_request) raise NoURLAvailableError
async def async_setup_entry(opp: OpenPeerPower, entry: ConfigEntry) -> bool: """Set up RainMachine as config entry.""" opp.data.setdefault(DOMAIN, {DATA_CONTROLLER: {}, DATA_COORDINATOR: {}}) opp.data[DOMAIN][DATA_COORDINATOR][entry.entry_id] = {} websession = aiohttp_client.async_get_clientsession(opp) client = Client(session=websession) try: await client.load_local( entry.data[CONF_IP_ADDRESS], entry.data[CONF_PASSWORD], port=entry.data[CONF_PORT], ssl=entry.data.get(CONF_SSL, DEFAULT_SSL), ) except RainMachineError as err: raise ConfigEntryNotReady from err # regenmaschine can load multiple controllers at once, but we only grab the one # we loaded above: controller = opp.data[DOMAIN][DATA_CONTROLLER][ entry.entry_id] = get_client_controller(client) entry_updates = {} if not entry.unique_id or is_ip_address(entry.unique_id): # If the config entry doesn't already have a unique ID, set one: entry_updates["unique_id"] = controller.mac if CONF_ZONE_RUN_TIME in entry.data: # If a zone run time exists in the config entry's data, pop it and move it to # options: data = {**entry.data} entry_updates["data"] = data entry_updates["options"] = { **entry.options, CONF_ZONE_RUN_TIME: data.pop(CONF_ZONE_RUN_TIME), } if entry_updates: opp.config_entries.async_update_entry(entry, **entry_updates) async def async_update(api_category: str) -> dict: """Update the appropriate API data based on a category.""" try: if api_category == DATA_PROGRAMS: return await controller.programs.all(include_inactive=True) if api_category == DATA_PROVISION_SETTINGS: return await controller.provisioning.settings() if api_category == DATA_RESTRICTIONS_CURRENT: return await controller.restrictions.current() if api_category == DATA_RESTRICTIONS_UNIVERSAL: return await controller.restrictions.universal() return await controller.zones.all(details=True, include_inactive=True) except RainMachineError as err: raise UpdateFailed(err) from err controller_init_tasks = [] for api_category in [ DATA_PROGRAMS, DATA_PROVISION_SETTINGS, DATA_RESTRICTIONS_CURRENT, DATA_RESTRICTIONS_UNIVERSAL, DATA_ZONES, ]: coordinator = opp.data[DOMAIN][DATA_COORDINATOR][ entry.entry_id][api_category] = DataUpdateCoordinator( opp, LOGGER, name=f'{controller.name} ("{api_category}")', update_interval=DEFAULT_UPDATE_INTERVAL, update_method=partial(async_update, api_category), ) controller_init_tasks.append(coordinator.async_refresh()) await asyncio.gather(*controller_init_tasks) opp.config_entries.async_setup_platforms(entry, PLATFORMS) entry.async_on_unload(entry.add_update_listener(async_reload_entry)) return True
def get_url( opp: OpenPeerPower, *, require_current_request: bool = False, require_ssl: bool = False, require_standard_port: bool = False, allow_internal: bool = True, allow_external: bool = True, allow_cloud: bool = True, allow_ip: bool = True, prefer_external: bool = False, prefer_cloud: bool = False, ) -> str: """Get a URL to this instance.""" if require_current_request and http.current_request.get() is None: raise NoURLAvailableError order = [TYPE_URL_INTERNAL, TYPE_URL_EXTERNAL] if prefer_external: order.reverse() # Try finding an URL in the order specified for url_type in order: if allow_internal and url_type == TYPE_URL_INTERNAL: with suppress(NoURLAvailableError): return _get_internal_url( opp, allow_ip=allow_ip, require_current_request=require_current_request, require_ssl=require_ssl, require_standard_port=require_standard_port, ) if allow_external and url_type == TYPE_URL_EXTERNAL: with suppress(NoURLAvailableError): return _get_external_url( opp, allow_cloud=allow_cloud, allow_ip=allow_ip, prefer_cloud=prefer_cloud, require_current_request=require_current_request, require_ssl=require_ssl, require_standard_port=require_standard_port, ) # For current request, we accept loopback interfaces (e.g., 127.0.0.1), # the Supervisor hostname and localhost transparently request_host = _get_request_host() if ( require_current_request and request_host is not None and opp.config.api is not None ): scheme = "https" if opp.config.api.use_ssl else "http" current_url = yarl.URL.build( scheme=scheme, host=request_host, port=opp.config.api.port ) known_hostnames = ["localhost"] if opp.components.oppio.is_oppio(): host_info = opp.components.oppio.get_host_info() known_hostnames.extend( [host_info["hostname"], f"{host_info['hostname']}.local"] ) if ( ( ( allow_ip and is_ip_address(request_host) and is_loopback(ip_address(request_host)) ) or request_host in known_hostnames ) and (not require_ssl or current_url.scheme == "https") and (not require_standard_port or current_url.is_default_port()) ): return normalize_url(str(current_url)) # We have to be honest now, we have no viable option available raise NoURLAvailableError