コード例 #1
0
class PlexFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
    """Handle a Plex config flow."""

    VERSION = 1
    CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_POLL

    @staticmethod
    @callback
    def async_get_options_flow(config_entry):
        """Get the options flow for this handler."""
        return PlexOptionsFlowHandler(config_entry)

    def __init__(self):
        """Initialize the Plex flow."""
        self.current_login = {}
        self.available_servers = None
        self.plexauth = None
        self.token = None
        self.client_id = None

    async def async_step_user(self, user_input=None):
        """Handle a flow initialized by the user."""
        return self.async_show_form(step_id="start_website_auth")

    async def async_step_start_website_auth(self, user_input=None):
        """Show a form before starting external authentication."""
        return await self.async_step_plex_website_auth()

    async def async_step_server_validate(self, server_config):
        """Validate a provided configuration."""
        errors = {}
        self.current_login = server_config

        plex_server = PlexServer(self.hass, server_config)
        try:
            await self.hass.async_add_executor_job(plex_server.connect)

        except NoServersFound:
            errors["base"] = "no_servers"
        except (plexapi.exceptions.BadRequest,
                plexapi.exceptions.Unauthorized):
            _LOGGER.error("Invalid credentials provided, config not created")
            errors["base"] = "faulty_credentials"
        except (plexapi.exceptions.NotFound,
                requests.exceptions.ConnectionError):
            server_identifier = (server_config.get(CONF_URL)
                                 or plex_server.server_choice or "Unknown")
            _LOGGER.error("Plex server could not be reached: %s",
                          server_identifier)
            errors["base"] = "not_found"

        except ServerNotSpecified as available_servers:
            self.available_servers = available_servers.args[0]
            return await self.async_step_select_server()

        except Exception as error:  # pylint: disable=broad-except
            _LOGGER.exception("Unknown error connecting to Plex server: %s",
                              error)
            return self.async_abort(reason="unknown")

        if errors:
            return self.async_show_form(step_id="start_website_auth",
                                        errors=errors)

        server_id = plex_server.machine_identifier

        for entry in self._async_current_entries():
            if entry.data[CONF_SERVER_IDENTIFIER] == server_id:
                return self.async_abort(reason="already_configured")

        url = plex_server.url_in_use
        token = server_config.get(CONF_TOKEN)

        entry_config = {CONF_URL: url}
        if self.client_id:
            entry_config[CONF_CLIENT_IDENTIFIER] = self.client_id
        if token:
            entry_config[CONF_TOKEN] = token
        if url.startswith("https"):
            entry_config[CONF_VERIFY_SSL] = server_config.get(
                CONF_VERIFY_SSL, DEFAULT_VERIFY_SSL)

        _LOGGER.debug("Valid config created for %s", plex_server.friendly_name)

        return self.async_create_entry(
            title=plex_server.friendly_name,
            data={
                CONF_SERVER: plex_server.friendly_name,
                CONF_SERVER_IDENTIFIER: server_id,
                PLEX_SERVER_CONFIG: entry_config,
            },
        )

    async def async_step_select_server(self, user_input=None):
        """Use selected Plex server."""
        config = dict(self.current_login)
        if user_input is not None:
            config[CONF_SERVER] = user_input[CONF_SERVER]
            return await self.async_step_server_validate(config)

        configured = configured_servers(self.hass)
        available_servers = [
            name for (name, server_id) in self.available_servers
            if server_id not in configured
        ]

        if not available_servers:
            return self.async_abort(reason="all_configured")
        if len(available_servers) == 1:
            config[CONF_SERVER] = available_servers[0]
            return await self.async_step_server_validate(config)

        return self.async_show_form(
            step_id="select_server",
            data_schema=vol.Schema(
                {vol.Required(CONF_SERVER): vol.In(available_servers)}),
            errors={},
        )

    async def async_step_discovery(self, discovery_info):
        """Set default host and port from discovery."""
        if self._async_current_entries() or self._async_in_progress():
            # Skip discovery if a config already exists or is in progress.
            return self.async_abort(reason="already_configured")

        json_file = self.hass.config.path(PLEX_CONFIG_FILE)
        file_config = await self.hass.async_add_executor_job(
            load_json, json_file)

        if file_config:
            host_and_port, host_config = file_config.popitem()
            prefix = "https" if host_config[CONF_SSL] else "http"

            server_config = {
                CONF_URL: f"{prefix}://{host_and_port}",
                CONF_TOKEN: host_config[CONF_TOKEN],
                CONF_VERIFY_SSL: host_config["verify"],
            }
            _LOGGER.info("Imported legacy config, file can be removed: %s",
                         json_file)
            return await self.async_step_server_validate(server_config)

        return self.async_abort(reason="discovery_no_file")

    async def async_step_import(self, import_config):
        """Import from Plex configuration."""
        _LOGGER.debug("Imported Plex configuration")
        return await self.async_step_server_validate(import_config)

    async def async_step_plex_website_auth(self):
        """Begin external auth flow on Plex website."""
        self.hass.http.register_view(PlexAuthorizationCallbackView)
        payload = {
            "X-Plex-Device-Name": X_PLEX_DEVICE_NAME,
            "X-Plex-Version": X_PLEX_VERSION,
            "X-Plex-Product": X_PLEX_PRODUCT,
            "X-Plex-Device": self.hass.config.location_name,
            "X-Plex-Platform": X_PLEX_PLATFORM,
            "X-Plex-Model": "Plex OAuth",
        }
        session = async_get_clientsession(self.hass)
        self.plexauth = PlexAuth(payload, session)
        await self.plexauth.initiate_auth()
        forward_url = f"{self.hass.config.api.base_url}{AUTH_CALLBACK_PATH}?flow_id={self.flow_id}"
        auth_url = self.plexauth.auth_url(forward_url)
        return self.async_external_step(step_id="obtain_token", url=auth_url)

    async def async_step_obtain_token(self, user_input=None):
        """Obtain token after external auth completed."""
        token = await self.plexauth.token(10)

        if not token:
            return self.async_external_step_done(next_step_id="timed_out")

        self.token = token
        self.client_id = self.plexauth.client_identifier
        return self.async_external_step_done(next_step_id="use_external_token")

    async def async_step_timed_out(self, user_input=None):
        """Abort flow when time expires."""
        return self.async_abort(reason="token_request_timeout")

    async def async_step_use_external_token(self, user_input=None):
        """Continue server validation with external token."""
        server_config = {CONF_TOKEN: self.token}
        return await self.async_step_server_validate(server_config)
コード例 #2
0
class PlexFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
    """Handle a Plex config flow."""

    VERSION = 1
    CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_PUSH

    @staticmethod
    @callback
    def async_get_options_flow(config_entry):
        """Get the options flow for this handler."""
        return PlexOptionsFlowHandler(config_entry)

    def __init__(self):
        """Initialize the Plex flow."""
        self.current_login = {}
        self.available_servers = None
        self.plexauth = None
        self.token = None
        self.client_id = None
        self._manual = False
        self._entry_id = None

    async def async_step_user(self, user_input=None, errors=None):  # pylint: disable=arguments-differ
        """Handle a flow initialized by the user."""
        if user_input is not None:
            return await self.async_step_plex_website_auth()
        if self.show_advanced_options:
            return await self.async_step_user_advanced(errors=errors)
        return self.async_show_form(step_id="user", errors=errors)

    async def async_step_user_advanced(self, user_input=None, errors=None):
        """Handle an advanced mode flow initialized by the user."""
        if user_input is not None:
            if user_input.get("setup_method") == MANUAL_SETUP_STRING:
                self._manual = True
                return await self.async_step_manual_setup()
            return await self.async_step_plex_website_auth()

        data_schema = vol.Schema({
            vol.Required("setup_method", default=AUTOMATIC_SETUP_STRING):
            vol.In([AUTOMATIC_SETUP_STRING, MANUAL_SETUP_STRING])
        })
        return self.async_show_form(step_id="user_advanced",
                                    data_schema=data_schema,
                                    errors=errors)

    async def async_step_manual_setup(self, user_input=None, errors=None):
        """Begin manual configuration."""
        if user_input is not None and errors is None:
            user_input.pop(CONF_URL, None)
            host = user_input.get(CONF_HOST)
            if host:
                port = user_input[CONF_PORT]
                prefix = "https" if user_input.get(CONF_SSL) else "http"
                user_input[CONF_URL] = f"{prefix}://{host}:{port}"
            elif CONF_TOKEN not in user_input:
                return await self.async_step_manual_setup(
                    user_input=user_input, errors={"base": "host_or_token"})
            return await self.async_step_server_validate(user_input)

        previous_input = user_input or {}

        data_schema = vol.Schema({
            vol.Optional(
                CONF_HOST,
                description={"suggested_value": previous_input.get(CONF_HOST)},
            ):
            str,
            vol.Required(CONF_PORT,
                         default=previous_input.get(CONF_PORT, DEFAULT_PORT)):
            int,
            vol.Required(CONF_SSL,
                         default=previous_input.get(CONF_SSL, DEFAULT_SSL)):
            bool,
            vol.Required(
                CONF_VERIFY_SSL,
                default=previous_input.get(CONF_VERIFY_SSL, DEFAULT_VERIFY_SSL),
            ):
            bool,
            vol.Optional(
                CONF_TOKEN,
                description={
                    "suggested_value": previous_input.get(CONF_TOKEN)
                },
            ):
            str,
        })
        return self.async_show_form(step_id="manual_setup",
                                    data_schema=data_schema,
                                    errors=errors)

    async def async_step_server_validate(self, server_config):
        """Validate a provided configuration."""
        errors = {}
        self.current_login = server_config

        plex_server = PlexServer(self.hass, server_config)
        try:
            await self.hass.async_add_executor_job(plex_server.connect)

        except NoServersFound:
            _LOGGER.error("No servers linked to Plex account")
            errors["base"] = "no_servers"
        except (plexapi.exceptions.BadRequest,
                plexapi.exceptions.Unauthorized):
            _LOGGER.error("Invalid credentials provided, config not created")
            errors[CONF_TOKEN] = "faulty_credentials"
        except requests.exceptions.SSLError as error:
            _LOGGER.error("SSL certificate error: [%s]", error)
            errors["base"] = "ssl_error"
        except (plexapi.exceptions.NotFound,
                requests.exceptions.ConnectionError):
            server_identifier = (server_config.get(CONF_URL)
                                 or plex_server.server_choice or "Unknown")
            _LOGGER.error("Plex server could not be reached: %s",
                          server_identifier)
            errors[CONF_HOST] = "not_found"

        except ServerNotSpecified as available_servers:
            self.available_servers = available_servers.args[0]
            return await self.async_step_select_server()

        except Exception as error:  # pylint: disable=broad-except
            _LOGGER.exception("Unknown error connecting to Plex server: %s",
                              error)
            return self.async_abort(reason="unknown")

        if errors:
            if self._manual:
                return await self.async_step_manual_setup(
                    user_input=server_config, errors=errors)
            return await self.async_step_user(errors=errors)

        server_id = plex_server.machine_identifier
        url = plex_server.url_in_use
        token = server_config.get(CONF_TOKEN)

        entry_config = {CONF_URL: url}
        if self.client_id:
            entry_config[CONF_CLIENT_ID] = self.client_id
        if token:
            entry_config[CONF_TOKEN] = token
        if url.startswith("https"):
            entry_config[CONF_VERIFY_SSL] = server_config.get(
                CONF_VERIFY_SSL, DEFAULT_VERIFY_SSL)

        data = {
            CONF_SERVER: plex_server.friendly_name,
            CONF_SERVER_IDENTIFIER: server_id,
            PLEX_SERVER_CONFIG: entry_config,
        }

        await self.async_set_unique_id(server_id)
        if (self.context[CONF_SOURCE]  # pylint: disable=no-member
                == config_entries.SOURCE_REAUTH):
            entry = self.hass.config_entries.async_get_entry(self._entry_id)
            self.hass.config_entries.async_update_entry(entry, data=data)
            _LOGGER.debug("Updated config entry for %s",
                          plex_server.friendly_name)
            await self.hass.config_entries.async_reload(entry.entry_id)
            return self.async_abort(reason="reauth_successful")

        self._abort_if_unique_id_configured()

        _LOGGER.debug("Valid config created for %s", plex_server.friendly_name)

        return self.async_create_entry(title=plex_server.friendly_name,
                                       data=data)

    async def async_step_select_server(self, user_input=None):
        """Use selected Plex server."""
        config = dict(self.current_login)
        if user_input is not None:
            config[CONF_SERVER] = user_input[CONF_SERVER]
            return await self.async_step_server_validate(config)

        configured = configured_servers(self.hass)
        available_servers = [
            name for (name, server_id) in self.available_servers
            if server_id not in configured
        ]

        if not available_servers:
            return self.async_abort(reason="all_configured")
        if len(available_servers) == 1:
            config[CONF_SERVER] = available_servers[0]
            return await self.async_step_server_validate(config)

        return self.async_show_form(
            step_id="select_server",
            data_schema=vol.Schema(
                {vol.Required(CONF_SERVER): vol.In(available_servers)}),
            errors={},
        )

    async def async_step_integration_discovery(self, discovery_info):
        """Handle GDM discovery."""
        machine_identifier = discovery_info["data"]["Resource-Identifier"]
        await self.async_set_unique_id(machine_identifier)
        self._abort_if_unique_id_configured()
        host = f"{discovery_info['from'][0]}:{discovery_info['data']['Port']}"
        name = discovery_info["data"]["Name"]
        self.context["title_placeholders"] = {  # pylint: disable=no-member
            "host": host,
            "name": name,
        }
        return await self.async_step_user()

    async def async_step_plex_website_auth(self):
        """Begin external auth flow on Plex website."""
        self.hass.http.register_view(PlexAuthorizationCallbackView)
        payload = {
            "X-Plex-Device-Name": X_PLEX_DEVICE_NAME,
            "X-Plex-Version": X_PLEX_VERSION,
            "X-Plex-Product": X_PLEX_PRODUCT,
            "X-Plex-Device": self.hass.config.location_name,
            "X-Plex-Platform": X_PLEX_PLATFORM,
            "X-Plex-Model": "Plex OAuth",
        }
        session = async_get_clientsession(self.hass)
        self.plexauth = PlexAuth(payload, session)
        await self.plexauth.initiate_auth()
        forward_url = f"{get_url(self.hass)}{AUTH_CALLBACK_PATH}?flow_id={self.flow_id}"
        auth_url = self.plexauth.auth_url(forward_url)
        return self.async_external_step(step_id="obtain_token", url=auth_url)

    async def async_step_obtain_token(self, user_input=None):
        """Obtain token after external auth completed."""
        token = await self.plexauth.token(10)

        if not token:
            return self.async_external_step_done(next_step_id="timed_out")

        self.token = token
        self.client_id = self.plexauth.client_identifier
        return self.async_external_step_done(next_step_id="use_external_token")

    async def async_step_timed_out(self, user_input=None):
        """Abort flow when time expires."""
        return self.async_abort(reason="token_request_timeout")

    async def async_step_use_external_token(self, user_input=None):
        """Continue server validation with external token."""
        server_config = {CONF_TOKEN: self.token}
        return await self.async_step_server_validate(server_config)

    async def async_step_reauth(self, data):
        """Handle a reauthorization flow request."""
        self.current_login = dict(data)
        self._entry_id = self.current_login.pop("config_entry_id")
        return await self.async_step_user()