Merge branch 'master' of git@83.233.175.44:hermes
[hermes] / package / src / org / maemo / hermes / engine / linkedin / api.py
index 76246eb..fe0c80f 100644 (file)
@@ -4,18 +4,21 @@ from oauth import oauth
 from xml.dom.minidom import parseString
 from org.maemo.hermes.engine.friend import Friend
 
-#    httplib.HTTPSConnection.debuglevel = 1
-
 class LinkedInApi():
     """LinkedIn API for Hermes.
                 
        Copyright (c) Fredrik Wendt <fredrik@wendt.se> 2010.
        Released under the Artistic Licence."""
        
-       
+    GCONF_API_KEY = '/apps/maemo/hermes/linkedin_key'
+    GCONF_API_SECRET = '/apps/maemo/hermes/linkedin_secret'
+    GCONF_ACCESS_TOKEN = '/apps/maemo/hermes/linkedin_access_token'
+    GCONF_USER = '/apps/maemo/hermes/linkedin_user'
+    
     LI_SERVER = "api.linkedin.com"
     LI_API_URL = "https://api.linkedin.com"
     LI_CONN_API_URL = LI_API_URL + "/v1/people/~/connections"
+    LI_PROFILE_API_URL = LI_API_URL + "/v1/people/~"
 
     REQUEST_TOKEN_URL = LI_API_URL + "/uas/oauth/requestToken"
     AUTHORIZE_URL = LI_API_URL + "/uas/oauth/authorize"
@@ -23,38 +26,33 @@ class LinkedInApi():
 
 
     # -----------------------------------------------------------------------
-    def __init__(self):
+    def __init__(self, gconf=None):
         """Initialize the LinkedIn service, finding LinkedIn API keys in gconf and
            having a gui_callback available."""
         
-        self._gc = gnome.gconf.client_get_default()
+        if gconf: self._gc = gconf
+        else: self._gc = gnome.gconf.client_get_default()
         
-        # -- Check the environment is going to work...
-        # FIXME: duplication
-        if (self._gc.get_string('/desktop/gnome/url-handlers/http/command') == 'epiphany %s'):
-            raise Exception('Browser in gconf invalid (see NB#136012). Installation error.')
-
-        api_key = self._gc.get_string('/apps/maemo/hermes/linkedin_api_key')
-        secret_key = self._gc.get_string('/apps/maemo/hermes/linkedin_key_secret')
+        api_key = self._gc.get_string(LinkedInApi.GCONF_API_KEY)
+        secret_key = self._gc.get_string(LinkedInApi.GCONF_API_SECRET)
+        self.api_key = api_key
 
         if api_key is None or secret_key is None:
             raise Exception('No LinkedIn application keys found. Installation error.')
 
-        self.access_token = self._get_access_token_from_gconf()
+        self.access_token = self.get_access_token_from_gconf()
 
         self.consumer = oauth.OAuthConsumer(api_key, secret_key)
         self.sig_method = oauth.OAuthSignatureMethod_HMAC_SHA1()
-        
-        # IMPROVEMENT: verify that the access_token is valid for at least another hour
-        self._verify_access_token()
-        
 
+
+    # -----------------------------------------------------------------------
     def authenticate(self, need_auth, block_for_auth):
         need_auth()
         token = self._get_request_token()
         url = self._get_authorize_url(token)
         verifier = block_for_auth(url)
-        self._verify_verifier(verifier)
+        self._verify_verifier(token, verifier)
     
     
     # -----------------------------------------------------------------------
@@ -69,46 +67,33 @@ class LinkedInApi():
         
 
     # -----------------------------------------------------------------------
-    def _verify_verifier(self, verifier):
-        try:
-            self.access_token = self._get_access_token(self.request_token, verifier)
-            self._store_access_token_in_gconf()
-        except:
-            raise Exception("authorization failed, try again")
-    
-        
-    # -----------------------------------------------------------------------
-    def _verify_access_token(self):
-        return True
-
-
-    # -----------------------------------------------------------------------
-    def _store_access_token_in_gconf(self, token_str):
-        self._gc.set_string('/apps/maemo/hermes/linkedin_access_token', token_str)
-
-        
-    # -----------------------------------------------------------------------
-    def _get_access_token_from_gconf(self):
-        token_str = self._gc.get_string('/apps/maemo/hermes/linkedin_access_token')
-        return oauth.OAuthToken.from_string(token_str)
+    def get_friend_details(self, url, header_value):
+        oauth_request = oauth.OAuthRequest.from_consumer_and_token(self.consumer, token=self.access_token, http_url=url)
+        oauth_request.sign_request(self.sig_method, self.consumer, self.access_token)
 
+        headers = oauth_request.to_header()
+        headers[u'x-li-auth-token'] = header_value
+        connection = httplib.HTTPConnection("api.linkedin.com")
+        connection.request(oauth_request.http_method, url, headers=headers)
+        data = connection.getresponse().read()
+        return data
     
+
     # -----------------------------------------------------------------------
     def _make_api_request(self, url):
-        print "_make_api_request", url
         oauth_request = oauth.OAuthRequest.from_consumer_and_token(self.consumer, token=self.access_token, http_url=url)
         oauth_request.sign_request(self.sig_method, self.consumer, self.access_token)
         connection = httplib.HTTPSConnection(self.LI_SERVER)
         try:
             connection.request(oauth_request.http_method, url, headers=oauth_request.to_header())
-            return connection.getresponse().read()
+            xml = connection.getresponse().read()
+            return xml
         except:
             raise Exception("Failed to contact LinkedIn at " + url)
 
 
     # -----------------------------------------------------------------------
     def _parse_dom(self, dom):
-        print "parse_dom", dom
         def get_first_tag(node, tagName):
             tags = node.getElementsByTagName(tagName)
             if tags and len(tags) > 0:
@@ -128,8 +113,12 @@ class LinkedInApi():
         # look for errors
         errors = dom.getElementsByTagName('error')
         if (len(errors) > 0):
-            print "Error" # FIXME: handle this better
-            return []
+            details = ""
+            try:
+                details = " (" + extract(errors[0], "message") + ")"
+            except:
+                pass
+            raise Exception("LinkedIn communication errors detected" + details)
         
         friends = []
         people = dom.getElementsByTagName('person')
@@ -150,13 +139,46 @@ class LinkedInApi():
                 friends.append(friend)
 
             except:
-                pass   
+                pass
+
+        return friends
+
+    # -----------------------------------------------------------------------
+    def _get_request_token(self):
+        """Get a request token from LinkedIn"""
+        
+        oauth_consumer_key = self.api_key
+        oauth_request = oauth.OAuthRequest.from_consumer_and_token(self.consumer, callback="oob", http_url=self.REQUEST_TOKEN_URL)
+        oauth_request.sign_request(self.sig_method, self.consumer, None)
+
+        connection = httplib.HTTPSConnection(self.LI_SERVER)
+        connection.request(oauth_request.http_method, self.REQUEST_TOKEN_URL, headers=oauth_request.to_header())
+        response = connection.getresponse().read()
+        
+        try:
+            token = oauth.OAuthToken.from_string(response)
+        except Exception, e:
+            import traceback
+            traceback.print_exc()
+            print response
+            raise Exception("Authorization failure - failed to get request token")
+        return token
 
     
     # -----------------------------------------------------------------------
+    def _get_authorize_url(self, token):
+        """The URL that the user should browse to, in order to authorize the 
+           application's request to access data"""
+        
+        oauth_request = oauth.OAuthRequest.from_token_and_callback(token=token, http_url=self.AUTHORIZE_URL)
+        return oauth_request.to_url()
+
+
+    # -----------------------------------------------------------------------
     def _get_access_token(self, token, verifier):
-        """If the verifier (which was displayed in the browser window) is valid,
-           then an access token is returned which should be used to access data on the service."""
+        """If the verifier (which was displayed in the browser window) is 
+           valid, then an access token is returned which should be used to 
+           access data on the service."""
         
         oauth_request = oauth.OAuthRequest.from_consumer_and_token(self.consumer, token=token, verifier=verifier, http_url=self.ACCESS_TOKEN_URL)
         oauth_request.sign_request(self.sig_method, self.consumer, token)
@@ -165,30 +187,47 @@ class LinkedInApi():
         connection.request(oauth_request.http_method, self.ACCESS_TOKEN_URL, headers=oauth_request.to_header()) 
         response = connection.getresponse()
         token_str = response.read()
+        if 'oauth_problem' in token_str:
+            raise Exception("Authorization failure - failed to get access token (" + token_str + ")")
         self._store_access_token_in_gconf(token_str)
         return oauth.OAuthToken.from_string(token_str)
 
 
     # -----------------------------------------------------------------------
-    def _get_authorize_url(self, token):
-        """The URL that the user should browse to, in order to authorize the application to acess data"""
-        
-        oauth_request = oauth.OAuthRequest.from_token_and_callback(token=token, http_url=self.AUTHORIZE_URL)
-        return oauth_request.to_url()
+    def _verify_verifier(self, request_token, verifier):
+        try:
+            self.access_token = self._get_access_token(request_token, verifier)
+            xml = self._make_api_request(self.LI_PROFILE_API_URL)
+            dom = parseString(xml)
+            friends = self._parse_dom(dom)
+            self._gc.set_string(LinkedInApi.GCONF_USER, friends[0].get_name())
+        except Exception, e:
+            import traceback
+            traceback.print_exc()
+            raise Exception("LinkedIn authorization failed, try again")
 
 
     # -----------------------------------------------------------------------
-    def _get_request_token(self):
-        """Get a request token from LinkedIn"""
+    def _store_access_token_in_gconf(self, token_str):
+        if "oauth_problem" in token_str:
+            raise Exception("Authorization failure - access token reported OAuth problem")
         
-        oauth_consumer_key = self.api_key
-        oauth_request = oauth.OAuthRequest.from_consumer_and_token(self.consumer, callback="oob", http_url=self.REQUEST_TOKEN_URL)
-        oauth_request.sign_request(self.sig_method, self.consumer, None)
+        self._gc.set_string(LinkedInApi.GCONF_ACCESS_TOKEN, token_str)
 
-        connection = httplib.HTTPSConnection(self.LI_SERVER)
-        connection.request(oauth_request.http_method, self.REQUEST_TOKEN_URL, headers=oauth_request.to_header())
-        response = self.connection.getresponse().read()
         
-        token = oauth.OAuthToken.from_string(response)
-        return token
+    # -----------------------------------------------------------------------
+    def get_access_token_from_gconf(self):
+        """Returns an oauth.OAuthToken, or None if the gconf value is empty"""
+        
+        token_str = self._gc.get_string(LinkedInApi.GCONF_ACCESS_TOKEN)
+        if not token_str:
+            return None
+        return oauth.OAuthToken.from_string(token_str)
+
 
+    # -----------------------------------------------------------------------
+    def remove_access_token_from_gconf(self):
+        """Remove the oauth.OAuthToken, if any."""
+        
+        self._gc.unset(LinkedInApi.GCONF_ACCESS_TOKEN)
+        self._gc.unset(LinkedInApi.GCONF_USER)