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))
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)
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)
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
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')
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
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
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
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)
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})' )
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})')
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))
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))