diff --git a/skpy/conn.py b/skpy/conn.py index ab3eef7..538299b 100644 --- a/skpy/conn.py +++ b/skpy/conn.py @@ -553,6 +553,16 @@ def auth(self, user, pwd): return json["skypetoken"], expiry +class LiveAuthSuccess(Exception): + """ + An exception used to capture the 't' value needed during Microsoft account authentication. + """ + + def __init__(self, t): + super(LiveAuthSuccess, self).__init__(t) + self.t = t + + class SkypeLiveAuthProvider(SkypeAuthProvider): """ An authentication provider that connects via Microsoft account authentication. @@ -591,79 +601,67 @@ def auth(self, user, pwd): .SkypeAuthException: if the login request is rejected .SkypeApiException: if the login form can't be processed """ - # Do the authentication dance. - params = self.getParams() - if isinstance(params, str): - t = params - else: - params["opid"] = self.sendCreds(user, pwd, params) - t = self.sendOpid(params) - return self.getToken(t) - - def getParams(self): - # First, start a Microsoft account login from Skype, which will redirect to login.live.com. - loginResp = self.conn("GET", "{0}/oauth/microsoft".format(SkypeConnection.API_LOGIN), - params={"client_id": "578134", "redirect_uri": "https://web.skype.com"}) - tField = BeautifulSoup(loginResp.text, "html.parser").find(id="t") + try: + self.getT(user, pwd) + except LiveAuthSuccess as ex: + return self.getToken(ex.t) + + def check(self, resp): + page = BeautifulSoup(resp.text, "html.parser") + # Look for the 't' value we need to exchange for a Skype token, which might turn up at any stage. + tField = page.find(id="t") if tField is not None: - # We've already got an existing session, no further steps needed. - return tField.get("value") - # This is inside some embedded JavaScript, so can't easily parse with BeautifulSoup. - ppftReg = re.search(r"""", "", errReg.group(1)).replace("\\'", "'").replace("\\\\", "\\") + raise SkypeApiException(errMsg, resp) + # Look for two-factor authentication device information (a non-empty array of factors) that we can't handle. + if re.search(r"\bV:\s*\[\s*{", resp.text): + raise SkypeAuthException("Two-factor authentication unsupported", resp) + # Look for a user consent form, meaning the user needs to accept terms or follow account security steps. + for form in page.findAll("form"): if form["name"] == "fmHF": url = form["action"].split("?", 1)[0] raise SkypeAuthException("Account action required ({0}), login with a web browser first" - .format(url), loginResp) - else: - raise SkypeApiException("Couldn't retrieve opid field from login response", loginResp) - - def sendOpid(self, params): - # Now repeat with the opid parameter. - loginResp = self.conn("POST", "{0}/ppsecure/post.srf".format(SkypeConnection.API_MSACC), - params={"wa": "wsignin1.0", "wp": "MBI_SSL", - "wreply": "https://lw.skype.com/login/oauth/proxy?client_id=578134&site_name=" - "lw.skype.com&redirect_uri=https%3A%2F%2Fweb.skype.com%2F"}, - cookies={"MSPRequ": params["MSPRequ"], - "MSPOK": params["MSPOK"], - "CkTst": str(int(time.time() * 1000))}, - data={"opid": params["opid"], - "site_name": "lw.skype.com", - "oauthPartner": "999", - "client_id": "578134", - "redirect_uri": "https://web.skype.com", - "PPFT": params["PPFT"], - "type": "28"}) - tField = BeautifulSoup(loginResp.text, "html.parser").find(id="t") - if tField is None: - err = re.search(r"sErrTxt:'([^'\\]*(\\.[^'\\]*)*)'", loginResp.text) - errMsg = "Couldn't retrieve t field from login response" - if err: - errMsg = re.sub(r"<.*?>", "", err.group(1)).replace("\\'", "'").replace("\\\\", "\\") or errMsg - raise SkypeApiException(errMsg, loginResp) - return tField.get("value") + .format(url), resp) + # No common elements, return the response for further processing. + return resp + + def getT(self, user, pwd): + # Stage 1: Start a Microsoft account login from Skype, which will redirect to login.live.com. + stage1Resp = self.check(self.conn("GET", "{0}/oauth/microsoft".format(SkypeConnection.API_LOGIN), + params={"client_id": "578134", "redirect_uri": "https://web.skype.com"})) + # This is inside some embedded JavaScript, so can't easily parse with BeautifulSoup. + ppftReg = re.search(r"""