Пример #1
0
    def generate_oauth(self,
                       email: Text = None,
                       password: Text = None,
                       refresh_token: Text = None) -> None:
        """Generate oauth header.

        Args
            email (Text, optional): Tesla account email address. Defaults to None.
            password (Text, optional): Password for account. Defaults to None.
            refresh_token (Text, optional): Refresh token. Defaults to None.

        Raises
            IncompleteCredentials

        Returns
            None

        """
        refresh_token = refresh_token or self.refresh_token
        self.oauth = {
            "client_id": self.client_id,
            "client_secret": self.client_secret
        }
        if email and password:
            self.oauth["grant_type"] = "password"
            self.oauth["email"] = email
            self.oauth["password"] = password
        elif refresh_token:
            self.oauth["grant_type"] = "refresh_token"
            self.oauth["refresh_token"] = refresh_token
        elif not refresh_token:
            raise IncompleteCredentials(
                "Missing oauth authentication details: refresh token.")
        else:
            raise IncompleteCredentials(
                "Missing oauth authentication details: email and password.")
Пример #2
0
async def test_form_invalid_auth_incomplete_credentials(hass):
    """Test we handle invalid auth with incomplete credentials."""
    result = await hass.config_entries.flow.async_init(
        DOMAIN, context={"source": config_entries.SOURCE_USER}
    )

    with patch(
        "custom_components.tesla_custom.config_flow.TeslaAPI.connect",
        side_effect=IncompleteCredentials(401),
    ):
        result2 = await hass.config_entries.flow.async_configure(
            result["flow_id"],
            {CONF_USERNAME: TEST_USERNAME, CONF_TOKEN: TEST_TOKEN},
        )

    assert result2["type"] == "form"
    assert result2["errors"] == {"base": "invalid_auth"}
Пример #3
0
 async def post(self, command, method="post", data=None):
     """Post data to API."""
     now = calendar.timegm(datetime.datetime.now().timetuple())
     _LOGGER.debug(
         "Token expiration in %s",
         str(datetime.timedelta(seconds=self.expiration - now)),
     )
     if now > self.expiration:
         self.token_refreshed = False
         auth = {}
         _LOGGER.debug("Oauth expiration detected")
         if (self.code or (self.email and self.password)) and (
                 not self.sso_oauth or
             (now > self.sso_oauth.get("expires_in", 0)
              and not self.sso_oauth.get("refresh_token"))):
             if self.email and self.password:
                 _LOGGER.debug("Getting sso auth code using credentials")
                 self.code = await self.get_authorization_code(
                     self.email, self.password, mfa_code=self.mfa_code)
             else:
                 _LOGGER.debug("Using existing authorization code")
             auth = await self.get_sso_auth_token(self.code)
         elif self.sso_oauth.get(
                 "refresh_token") and now > self.sso_oauth.get(
                     "expires_in", 0):
             _LOGGER.debug("Refreshing sso auth code")
             auth = await self.refresh_access_token(
                 refresh_token=self.sso_oauth.get("refresh_token"))
         if auth and all(
             (auth.get(item)
              for item in ["access_token", "refresh_token", "expires_in"])):
             self.sso_oauth = {
                 "access_token": auth["access_token"],
                 "refresh_token": auth["refresh_token"],
                 "expires_in": auth["expires_in"] + now,
             }
             _LOGGER.debug("Saved new auth info %s", self.sso_oauth)
         else:
             _LOGGER.debug("Unable to refresh sso oauth token")
             if auth:
                 _LOGGER.debug("Auth returned %s", auth)
             self.code = None
             self.sso_oauth = {}
             raise IncompleteCredentials("Need oauth credentials")
         auth = await self.get_bearer_token(
             access_token=self.sso_oauth.get("access_token"))
         _LOGGER.debug("Received bearer token %s", auth)
         if auth.get("created_at"):
             # use server time if available
             self.__sethead(
                 access_token=auth["access_token"],
                 expiration=auth["expires_in"] + auth["created_at"],
             )
         else:
             self.__sethead(access_token=auth["access_token"],
                            expires_in=auth["expires_in"])
         self.refresh_token = auth["refresh_token"]
         self.token_refreshed = True
         _LOGGER.debug("Successfully refreshed oauth")
     return await self.__open(f"{self.api}{command}",
                              method=method,
                              headers=self.head,
                              data=data)
Пример #4
0
 async def get_authorization_code(
     self,
     email: Text,
     password: Text,
     mfa_code: Text = "",
     mfa_device: int = 0,
     retry_limit: int = 3,
 ) -> Text:
     """Get authorization code from the oauth3 login method."""
     # https://tesla-api.timdorr.com/api-basics/authentication#step-2-obtain-an-authorization-code
     # pylint: disable=too-many-locals
     if not (email and password):
         _LOGGER.debug("No email or password for login; unable to login.")
         return
     url = self.get_authorization_code_link(new=True)
     resp = await self.websession.get(
         url.update_query({"login_hint": email}))
     html = await resp.text()
     if resp.history:
         for item in resp.history:
             if (item.status in [301, 302, 303, 304, 305, 306, 307, 308]
                     and resp.url.host != self.auth_domain.host):
                 _LOGGER.debug(
                     "Detected %s redirect from %s to %s; changing proxy host",
                     item.status,
                     item.url.host,
                     resp.url.host,
                 )
                 self.auth_domain = self.auth_domain.with_host(
                     str(resp.url.host))
                 url = self.get_authorization_code_link()
     soup: BeautifulSoup = BeautifulSoup(html, "html.parser")
     data = get_inputs(soup)
     data["identity"] = email
     data["credential"] = password
     transaction_id: Text = data.get("transaction_id")
     for attempt in range(retry_limit):
         _LOGGER.debug("Attempt #%s", attempt)
         resp = await self.websession.post(url, data=data)
         _process_resp(resp)
         if not resp.history:
             html = await resp.text()
             if "/mfa/verify" in html:
                 _LOGGER.debug("Detected MFA request")
                 mfa_resp = await self.websession.get(
                     self.auth_domain.with_path(
                         "/oauth2/v3/authorize/mfa/factors"),
                     params={"transaction_id": transaction_id},
                 )
                 _process_resp(mfa_resp)
                 # {
                 #     "data": [
                 #         {
                 #         "dispatchRequired": false,
                 #         "id": "X-4Y-44e4-b9a4-54e114a13c40",
                 #         "name": "Pixel",
                 #         "factorType": "token:software",
                 #         "factorProvider": "TESLA",
                 #         "securityLevel": 1,
                 #         "activatedAt": "2021-02-10T23:53:40.000Z",
                 #         "updatedAt": "2021-02-10T23:54:20.000Z"
                 #         }
                 #     ]
                 # }
                 mfa_json = await mfa_resp.json()
                 if len(mfa_json.get("data", [])) >= 1:
                     factor_id = mfa_json["data"][mfa_device]["id"]
                 if not mfa_code:
                     _LOGGER.debug("No MFA provided")
                     _LOGGER.debug("MFA Devices: %s", mfa_json["data"])
                     raise IncompleteCredentials("MFA Code missing",
                                                 devices=mfa_json["data"])
                 mfa_resp = await self.websession.post(
                     self.auth_domain.with_path(
                         "/oauth2/v3/authorize/mfa/verify"),
                     json={
                         "transaction_id": transaction_id,
                         "factor_id": factor_id,
                         "passcode": mfa_code,
                     },
                 )
                 _process_resp(mfa_resp)
                 mfa_json = await mfa_resp.json()
                 if not (mfa_json["data"].get("approved")
                         and mfa_json["data"].get("valid")):
                     _LOGGER.debug("MFA Code invalid")
                     raise IncompleteCredentials("MFA Code invalid",
                                                 devices=mfa_json["data"])
                 resp = await self.websession.post(url, data=data)
                 _process_resp(resp)
         await asyncio.sleep(3)
     if not resp.history or not URL(resp.history[-1].url).query.get("code"):
         _LOGGER.debug("Failed to authenticate")
         raise IncompleteCredentials("Unable to login with credentials")
     code_url = URL(resp.history[-1].url)
     _LOGGER.debug("Found code %s", code_url.query.get("code"))
     return code_url.query.get("code")
Пример #5
0
 async def need_reauth(self):
     """Raise incomplete credentials."""
     raise IncompleteCredentials("TEST")