@@ -553,6 +553,16 @@ def auth(self, user, pwd):
553
553
return json ["skypetoken" ], expiry
554
554
555
555
556
+ class LiveAuthSuccess (Exception ):
557
+ """
558
+ An exception used to capture the 't' value needed during Microsoft account authentication.
559
+ """
560
+
561
+ def __init__ (self , t ):
562
+ super (LiveAuthSuccess , self ).__init__ (t )
563
+ self .t = t
564
+
565
+
556
566
class SkypeLiveAuthProvider (SkypeAuthProvider ):
557
567
"""
558
568
An authentication provider that connects via Microsoft account authentication.
@@ -591,79 +601,67 @@ def auth(self, user, pwd):
591
601
.SkypeAuthException: if the login request is rejected
592
602
.SkypeApiException: if the login form can't be processed
593
603
"""
594
- # Do the authentication dance.
595
- params = self .getParams ()
596
- if isinstance (params , str ):
597
- t = params
598
- else :
599
- params ["opid" ] = self .sendCreds (user , pwd , params )
600
- t = self .sendOpid (params )
601
- return self .getToken (t )
602
-
603
- def getParams (self ):
604
- # First, start a Microsoft account login from Skype, which will redirect to login.live.com.
605
- loginResp = self .conn ("GET" , "{0}/oauth/microsoft" .format (SkypeConnection .API_LOGIN ),
606
- params = {"client_id" : "578134" , "redirect_uri" : "https://web.skype.com" })
607
- tField = BeautifulSoup (loginResp .text , "html.parser" ).find (id = "t" )
604
+ try :
605
+ self .getT (user , pwd )
606
+ except LiveAuthSuccess as ex :
607
+ return self .getToken (ex .t )
608
+
609
+ def check (self , resp ):
610
+ page = BeautifulSoup (resp .text , "html.parser" )
611
+ # Look for the 't' value we need to exchange for a Skype token, which might turn up at any stage.
612
+ tField = page .find (id = "t" )
608
613
if tField is not None :
609
- # We've already got an existing session, no further steps needed.
610
- return tField .get ("value" )
611
- # This is inside some embedded JavaScript, so can't easily parse with BeautifulSoup.
612
- ppftReg = re .search (r"""<input.*?name="PPFT".*?value="(.*?)""" + "\" " , loginResp .text )
613
- if not ppftReg :
614
- raise SkypeApiException ("Couldn't retrieve PPFT from login form" , loginResp )
615
- if "MSPRequ" not in loginResp .cookies or "MSPOK" not in loginResp .cookies :
616
- raise SkypeApiException ("Couldn't retrieve MSPRequ/MSPOK cookies" , loginResp )
617
- return {"MSPRequ" : loginResp .cookies .get ("MSPRequ" ),
618
- "MSPOK" : loginResp .cookies .get ("MSPOK" ),
619
- "PPFT" : ppftReg .group (1 )}
620
-
621
- def sendCreds (self , user , pwd , params ):
622
- # Now pass the login credentials over.
623
- loginResp = self .conn ("POST" , "{0}/ppsecure/post.srf" .format (SkypeConnection .API_MSACC ),
624
- params = {"wa" : "wsignin1.0" , "wp" : "MBI_SSL" ,
625
- "wreply" : "https://lw.skype.com/login/oauth/proxy?client_id=578134&site_name="
626
- "lw.skype.com&redirect_uri=https%3A%2F%2Fweb.skype.com%2F" },
627
- cookies = {"MSPRequ" : params ["MSPRequ" ], "MSPOK" : params ["MSPOK" ],
628
- "CkTst" : "G{0}" .format (int (time .time () * 1000 ))},
629
- data = {"login" : user , "passwd" : pwd ,
630
- "PPFT" : params ["PPFT" ], "loginoptions" : "3" })
631
- opid = re .search (r"""opid=([A-Z0-9]+)""" , loginResp .text , re .I )
632
- if opid :
633
- return opid .group (1 )
634
- loginPage = BeautifulSoup (loginResp .text , "html.parser" )
635
- for form in loginPage .findAll ("form" ):
614
+ raise LiveAuthSuccess (tField .get ("value" ))
615
+ # Look for an error message within the response.
616
+ errReg = re .search (r"sErrTxt:'([^'\\]*(\\.[^'\\]*)+)'" , resp .text )
617
+ if errReg :
618
+ errMsg = re .sub (r"<.*?>" , "" , errReg .group (1 )).replace ("\\ '" , "'" ).replace ("\\ \\ " , "\\ " )
619
+ raise SkypeApiException (errMsg , resp )
620
+ # Look for two-factor authentication device information (a non-empty array of factors) that we can't handle.
621
+ if re .search (r"\bV:\s*\[\s*{" , resp .text ):
622
+ raise SkypeAuthException ("Two-factor authentication unsupported" , resp )
623
+ # Look for a user consent form, meaning the user needs to accept terms or follow account security steps.
624
+ for form in page .findAll ("form" ):
636
625
if form ["name" ] == "fmHF" :
637
626
url = form ["action" ].split ("?" , 1 )[0 ]
638
627
raise SkypeAuthException ("Account action required ({0}), login with a web browser first"
639
- .format (url ), loginResp )
640
- else :
641
- raise SkypeApiException ("Couldn't retrieve opid field from login response" , loginResp )
642
-
643
- def sendOpid (self , params ):
644
- # Now repeat with the opid parameter.
645
- loginResp = self .conn ("POST" , "{0}/ppsecure/post.srf" .format (SkypeConnection .API_MSACC ),
646
- params = {"wa" : "wsignin1.0" , "wp" : "MBI_SSL" ,
647
- "wreply" : "https://lw.skype.com/login/oauth/proxy?client_id=578134&site_name="
648
- "lw.skype.com&redirect_uri=https%3A%2F%2Fweb.skype.com%2F" },
649
- cookies = {"MSPRequ" : params ["MSPRequ" ],
650
- "MSPOK" : params ["MSPOK" ],
651
- "CkTst" : str (int (time .time () * 1000 ))},
652
- data = {"opid" : params ["opid" ],
653
- "site_name" : "lw.skype.com" ,
654
- "oauthPartner" : "999" ,
655
- "client_id" : "578134" ,
656
- "redirect_uri" : "https://web.skype.com" ,
657
- "PPFT" : params ["PPFT" ],
658
- "type" : "28" })
659
- tField = BeautifulSoup (loginResp .text , "html.parser" ).find (id = "t" )
660
- if tField is None :
661
- err = re .search (r"sErrTxt:'([^'\\]*(\\.[^'\\]*)*)'" , loginResp .text )
662
- errMsg = "Couldn't retrieve t field from login response"
663
- if err :
664
- errMsg = re .sub (r"<.*?>" , "" , err .group (1 )).replace ("\\ '" , "'" ).replace ("\\ \\ " , "\\ " ) or errMsg
665
- raise SkypeApiException (errMsg , loginResp )
666
- return tField .get ("value" )
628
+ .format (url ), resp )
629
+ # No common elements, return the response for further processing.
630
+ return resp
631
+
632
+ def getT (self , user , pwd ):
633
+ # Stage 1: Start a Microsoft account login from Skype, which will redirect to login.live.com.
634
+ stage1Resp = self .check (self .conn ("GET" , "{0}/oauth/microsoft" .format (SkypeConnection .API_LOGIN ),
635
+ params = {"client_id" : "578134" , "redirect_uri" : "https://web.skype.com" }))
636
+ # This is inside some embedded JavaScript, so can't easily parse with BeautifulSoup.
637
+ ppftReg = re .search (r"""<input.*?name="PPFT".*?value="(.*?)""" + "\" " , stage1Resp .text )
638
+ if not ppftReg :
639
+ raise SkypeApiException ("Couldn't retrieve PPFT from login form" , stage1Resp )
640
+ ppft = ppftReg .group (1 )
641
+ if "MSPRequ" not in stage1Resp .cookies or "MSPOK" not in stage1Resp .cookies :
642
+ raise SkypeApiException ("Couldn't retrieve MSPRequ/MSPOK cookies" , stage1Resp )
643
+ # Prepare the Live login page request parameters.
644
+ params = {"wa" : "wsignin1.0" , "wp" : "MBI_SSL" ,
645
+ "wreply" : "https://lw.skype.com/login/oauth/proxy?client_id=578134&site_name="
646
+ "lw.skype.com&redirect_uri=https%3A%2F%2Fweb.skype.com%2F" }
647
+ cookies = {"MSPRequ" : stage1Resp .cookies .get ("MSPRequ" ), "MSPOK" : stage1Resp .cookies .get ("MSPOK" )}
648
+ # Stage 2: Submit the user's credentials.
649
+ stage2Resp = self .check (self .conn ("POST" , "{0}/ppsecure/post.srf" .format (SkypeConnection .API_MSACC ),
650
+ params = params ,
651
+ cookies = dict (cookies , CkTst = "G{0}" .format (int (time .time () * 1000 ))),
652
+ data = {"login" : user , "passwd" : pwd , "PPFT" : ppft , "loginoptions" : "3" }))
653
+ opidReg = re .search (r"""opid=([A-Z0-9]+)""" , stage2Resp .text , re .I )
654
+ if not opidReg :
655
+ raise SkypeApiException ("Couldn't retrieve opid field from login response" , stage2Resp )
656
+ # Stage 3: Repeat with the 'opid' parameter.
657
+ stage3Resp = self .check (self .conn ("POST" , "{0}/ppsecure/post.srf" .format (SkypeConnection .API_MSACC ),
658
+ params = params ,
659
+ cookies = dict (cookies , CkTst = "G{0}" .format (int (time .time () * 1000 ))),
660
+ data = {"opid" : opidReg .group (1 ), "PPFT" : ppft , "site_name" : "lw.skype.com" ,
661
+ "oauthPartner" : "999" , "client_id" : "578134" ,
662
+ "redirect_uri" : "https://web.skype.com" , "type" : "28" }))
663
+ # No check matches, and no further actions we can take.
664
+ raise SkypeApiException ("Couldn't retrieve t field from login response" , stage3Resp )
667
665
668
666
def getToken (self , t ):
669
667
# Now exchange the 't' value for a Skype token.
0 commit comments