Esempio n. 1
0
    def _login_request(self):
        try:
            self._init_session()
            sign = self._login_step1()
            if not sign.startswith('http'):
                location = self._login_step2(sign)
            else:
                location = sign  # we already have login location

            response3 = self._login_step3(location)
            if response3.status_code == 403:
                raise MiCloudException(
                    "Access denied. Did you set the correct api key and/or username?"
                )
            elif response3.status_code == 200:
                logging.debug("Your service token: %s", self.service_token)
                return True
            else:
                logging.debug(
                    "request returned status '%s', reason: %s, content: %s",
                    response3.status_code, response3.reason, response3.text)
                raise MiCloudException(response3.status_code +
                                       response3.reason)
        except Exception as e:
            raise MiCloudException("Cannot logon to Xiaomi cloud: " + str(e))
Esempio n. 2
0
    def _login_step1(self):
        logging.debug("Xiaomi login step 1")

        url = "https://account.xiaomi.com/pass/serviceLogin?sid=xiaomiio&_json=true"
        self.session.cookies.update(
            {'userId': self.user_id if self.user_id else self.username})
        response = self.session.get(url)
        response_json = json.loads(response.text.replace("&&&START&&&", ""))

        logging.debug("Xiaomi login step 1 response code: %s",
                      response.status_code)
        logging.debug("Xiaomi login step 1 response: %s",
                      json.dumps(response_json))

        try:
            if "_sign" in response_json:
                sign = response_json["_sign"]
                logging.debug("Xiaomi login step 1 sign: %s", sign)
                return sign
            else:
                logging.debug(
                    "Xiaomi login _sign missing. Maybe still has login cookie."
                )
                return ""

        except Exception as e:
            raise MiCloudException(
                "Error getting logon sign. Cannot parse response.", e)
Esempio n. 3
0
    def __init__(self, username, password):
        super().__init__()
        self.user_id = None
        self.service_token = None
        self.session = None
        self.ssecurity = None
        self.cuser_id = None
        self.pass_token = None

        self.failed_logins = 0

        self.agent_id = miutils.get_random_agent_id()
        self.useragent = "Android-7.1.1-1.0.0-ONEPLUS A3010-136-" + self.agent_id + " APP/xiaomi.smarthome APPV/62830"
        self.locale = locale.getdefaultlocale()[0]

        timezone = datetime.datetime.now(
            tzlocal.get_localzone()).strftime('%z')
        timezone = "GMT{0}:{1}".format(timezone[:-2], timezone[-2:])
        self.timezone = timezone

        self.default_server = 'de'  # Sets default server to Europe.
        self.username = username
        self.password = password
        if not self._check_credentials():
            raise MiCloudException("username or password can't be empty")

        self.client_id = miutils.get_random_string(6)
Esempio n. 4
0
    def api_session(self):
        if not self.service_token or not self.user_id:
            raise MiCloudException(
                'Cannot execute request. service token or userId missing. Make sure to login.'
            )

        session = requests.Session()
        session.headers.update({
            'X-XIAOMI-PROTOCAL-FLAG-CLI': 'PROTOCAL-HTTP2',
            'Content-Type': 'application/x-www-form-urlencoded',
            'User-Agent': self.useragent,
        })
        session.cookies.update({
            'userId':
            str(self.user_id),
            'yetAnotherServiceToken':
            self.service_token,
            'serviceToken':
            self.service_token,
            'locale':
            str(self.locale),
            'timezone':
            str(self.timezone),
            'is_daylight':
            str(time.daylight),
            'dst_offset':
            str(time.localtime().tm_isdst * 60 * 60 * 1000),
            'channel':
            'MI_APP_STORE'
        })
        return session
Esempio n. 5
0
def gen_signature(url, signed_nonce, nonce, params):
    if not signed_nonce or len(signed_nonce) == 0:
        raise MiCloudException("signed_nonce is required.")

    exps = []
    if url:
        uri = urlparse(url)
        exps.append(uri.path)

    exps.append(signed_nonce)
    exps.append(nonce)

    if params:
        for key in sorted (params) : 
            exps.append("%s=%s" % (key, params.get(key)))
    
    sign = ""
    first = True
    for s in exps:
        if not first:
            sign = sign + "&"
        else:
            first = False
        
        sign = sign + s

    signature = hmac.new(base64.b64decode(bytes(signed_nonce, 'utf-8')), msg = bytes(sign, 'utf-8'), digestmod = hashlib.sha256).digest()
    base64_bytes = base64.b64encode(signature)
    return base64_bytes.decode('utf-8')
Esempio n. 6
0
 def request_miot_spec(self, api, params=None):
     rdt = self.request_miot_api('miotspec/' + api, {
         'params': params or [],
     }) or {}
     rls = rdt.get('result')
     if not rls and rdt.get('code'):
         raise MiCloudException(json.dumps(rdt))
     return rls
Esempio n. 7
0
 async def async_login(self, captcha=None):
     if self.login_times > 10:
         raise MiCloudException(
             'Too many failures when login to Xiaomi, '
             'please reload/config xiaomi_miot component.')
     self.login_times += 1
     ret = await self.hass.async_add_executor_job(self._login_request,
                                                  captcha)
     if ret:
         self.hass.data[DOMAIN]['sessions'][self.unique_id] = self
         await self.async_stored_auth(self.user_id, save=True)
         self.login_times = 0
     return ret
Esempio n. 8
0
 def _login_step1(self):
     response = self.session.get(
         f'{ACCOUNT_BASE}/pass/serviceLogin',
         params={
             'sid': self.sid,
             '_json': 'true'
         },
     )
     try:
         auth = json.loads(response.text.replace('&&&START&&&', '')) or {}
     except Exception as exc:
         raise MiCloudException(
             f'Error getting xiaomi login sign. Cannot parse response. {exc}'
         )
     return auth
Esempio n. 9
0
    def _login_step2(self, sign):
        logging.debug("Xiaomi login step 2")

        url = "https://account.xiaomi.com/pass/serviceLoginAuth2"
        post_data = {
            'sid': "xiaomiio",
            'hash': hashlib.md5(self.password.encode()).hexdigest().upper(),
            'callback': "https://sts.api.io.mi.com/sts",
            'qs': '%3Fsid%3Dxiaomiio%26_json%3Dtrue',
            'user': self.username,
            '_json': 'true'
        }
        if sign:
            post_data['_sign'] = sign

        response = self.session.post(url, data=post_data)
        response_json = json.loads(response.text.replace("&&&START&&&", ""))

        logging.debug("Xiaomi login step 2 response code: %s",
                      response.status_code)
        logging.debug("Xiaomi login step 2 response: %s",
                      json.dumps(response_json))

        if response_json['result'] != "ok":
            raise MiCloudAccessDenied(
                "Access denied. Did you set the correct api key and/or username?"
            )

        self.user_id = response_json['userId']
        self.ssecurity = response_json['ssecurity']
        self.cuser_id = response_json['cUserId']
        self.pass_token = response_json['passToken']

        location = response_json['location']
        code = response_json['code']

        logging.debug("Xiaomi login ssecurity: %s", self.ssecurity)
        logging.debug("Xiaomi login userId: %s", self.user_id)
        logging.debug("Xiaomi login cUserId: %s", self.cuser_id)
        logging.debug("Xiaomi login passToken: %s", self.pass_token)
        logging.debug("Xiaomi login location: %s", location)
        logging.debug("Xiaomi login code: %s", code)

        if location:
            return location
        else:
            raise MiCloudException(
                "Error getting logon location URL. Return code: " + code)
Esempio n. 10
0
 def _login_request(self):
     self._init_session()
     sign = self._login_step1()
     if not sign.startswith('http'):
         location = self._login_step2(sign)
     else:
         location = sign  # we already have login location
     response3 = self._login_step3(location)
     if response3.status_code == 403:
         raise MiCloudAccessDenied(
             'Access denied. Did you set the correct username/password ?')
     elif response3.status_code == 200:
         return True
     else:
         _LOGGER.warning(
             'Xiaomi login request returned status %s, reason: %s, content: %s',
             response3.status_code,
             response3.reason,
             response3.text,
         )
         raise MiCloudException(
             f'Login to xiaomi error: {response3.text} ({response3.status_code})'
         )
Esempio n. 11
0
 def _login_request(self, captcha=None):
     self._init_session()
     auth = self.attrs.pop('login_data', None)
     if captcha and auth:
         auth['captcha'] = captcha
     if not auth:
         auth = self._login_step1()
     location = self._login_step2(**auth)
     response = self._login_step3(location)
     http_code = response.status_code
     if http_code == 200:
         return True
     elif http_code == 403:
         raise MiCloudAccessDenied(
             f'Login to xiaomi error: {response.text} ({http_code})')
     else:
         _LOGGER.error(
             'Xiaomi login request returned status %s, reason: %s, content: %s',
             http_code,
             response.reason,
             response.text,
         )
         raise MiCloudException(
             f'Login to xiaomi error: {response.text} ({http_code})')
Esempio n. 12
0
    def request(self, url, params):
        if not self.service_token or not self.user_id:
            raise MiCloudException(
                "Cannot execute request. service token or userId missing. Make sure to login."
            )

        self.session = requests.Session()
        self.session.headers.update({'User-Agent': self.useragent})

        logging.debug("Send request: %s to %s", params['data'], url)
        self.session.headers.update({
            'x-xiaomi-protocal-flag-cli':
            'PROTOCAL-HTTP2',
            'content-type':
            'application/x-www-form-urlencoded'
        })
        self.session.cookies.update({
            'userId':
            str(self.user_id),
            'yetAnotherServiceToken':
            self.service_token,
            'serviceToken':
            self.service_token,
            'locale':
            str(self.locale),
            'timezone':
            str(self.timezone),
            'is_daylight':
            str(time.daylight),
            'dst_offset':
            str(time.localtime().tm_isdst * 60 * 60 * 1000),
            'channel':
            'MI_APP_STORE'
        })
        for c in self.session.cookies:
            logging.debug('Cookie: %s', c)

        try:
            nonce = miutils.gen_nonce()
            signed_nonce = miutils.signed_nonce(self.ssecurity, nonce)
            signature = miutils.gen_signature(url.replace("/app", ""),
                                              signed_nonce, nonce, params)

            post_data = {
                'signature': signature,
                '_nonce': nonce,
                'data': params['data']
            }

            response = self.session.post(url, data=post_data)
            if response.status_code == 403:
                self.service_token = None

            return response.text
        except requests.exceptions.HTTPError as e:
            self.service_token = None
            logging.exception("Error while executing request to %s :%s", url,
                              str(e))
        except MiCloudException as e:
            logging.exception(
                "Error while decrypting response of request to %s :%s", url,
                str(e))
        except Exception as e:
            logging.exception("Error while executing request to %s :%s", url,
                              str(e))
Esempio n. 13
0
    def request(self, url, params):
        if not self.service_token or not self.user_id:
            raise MiCloudException(
                "Cannot execute request. service token or userId missing. Make sure to login."
            )

        logging.debug("Send request: %s to %s", params['data'], url)

        self.session = requests.Session()
        self.session.headers.update({
            'User-Agent': self.useragent,
            'Accept-Encoding': 'identity',
            'x-xiaomi-protocal-flag-cli': 'PROTOCAL-HTTP2',
            'content-type': 'application/x-www-form-urlencoded',
            'MIOT-ENCRYPT-ALGORITHM': 'ENCRYPT-RC4'
        })
        self.session.cookies.update({
            'userId':
            str(self.user_id),
            'yetAnotherServiceToken':
            self.service_token,
            'serviceToken':
            self.service_token,
            'locale':
            str(self.locale),
            'timezone':
            str(self.timezone),
            'is_daylight':
            str(time.daylight),
            'dst_offset':
            str(time.localtime().tm_isdst * 60 * 60 * 1000),
            'channel':
            'MI_APP_STORE'
        })
        for c in self.session.cookies:
            logging.debug('Cookie: %s', c)

        try:
            nonce = miutils.gen_nonce()
            signed_nonce = miutils.signed_nonce(self.ssecurity, nonce)
            post_data = miutils.generate_enc_params(url, "POST", signed_nonce,
                                                    nonce, params,
                                                    self.ssecurity)

            response = self.session.post(url, data=post_data)
            if response.status_code == 403:
                self.service_token = None

            return miutils.decrypt_rc4(
                miutils.signed_nonce(self.ssecurity, post_data["_nonce"]),
                response.text)
        except requests.exceptions.HTTPError as e:
            self.service_token = None
            logging.exception("Error while executing request to %s :%s", url,
                              str(e))
        except MiCloudException as e:
            logging.exception(
                "Error while decrypting response of request to %s :%s", url,
                str(e))
        except Exception as e:
            logging.exception("Error while executing request to %s :%s", url,
                              str(e))