fe0c80f2cf8152612892508e8c73e285cec5c92a
[hermes] / package / src / org / maemo / hermes / engine / linkedin / api.py
1 import httplib
2 import gnome.gconf
3 from oauth import oauth
4 from xml.dom.minidom import parseString
5 from org.maemo.hermes.engine.friend import Friend
6
7 class LinkedInApi():
8     """LinkedIn API for Hermes.
9                 
10        Copyright (c) Fredrik Wendt <fredrik@wendt.se> 2010.
11        Released under the Artistic Licence."""
12        
13     GCONF_API_KEY = '/apps/maemo/hermes/linkedin_key'
14     GCONF_API_SECRET = '/apps/maemo/hermes/linkedin_secret'
15     GCONF_ACCESS_TOKEN = '/apps/maemo/hermes/linkedin_access_token'
16     GCONF_USER = '/apps/maemo/hermes/linkedin_user'
17     
18     LI_SERVER = "api.linkedin.com"
19     LI_API_URL = "https://api.linkedin.com"
20     LI_CONN_API_URL = LI_API_URL + "/v1/people/~/connections"
21     LI_PROFILE_API_URL = LI_API_URL + "/v1/people/~"
22
23     REQUEST_TOKEN_URL = LI_API_URL + "/uas/oauth/requestToken"
24     AUTHORIZE_URL = LI_API_URL + "/uas/oauth/authorize"
25     ACCESS_TOKEN_URL = LI_API_URL + "/uas/oauth/accessToken"
26
27
28     # -----------------------------------------------------------------------
29     def __init__(self, gconf=None):
30         """Initialize the LinkedIn service, finding LinkedIn API keys in gconf and
31            having a gui_callback available."""
32         
33         if gconf: self._gc = gconf
34         else: self._gc = gnome.gconf.client_get_default()
35         
36         api_key = self._gc.get_string(LinkedInApi.GCONF_API_KEY)
37         secret_key = self._gc.get_string(LinkedInApi.GCONF_API_SECRET)
38         self.api_key = api_key
39
40         if api_key is None or secret_key is None:
41             raise Exception('No LinkedIn application keys found. Installation error.')
42
43         self.access_token = self.get_access_token_from_gconf()
44
45         self.consumer = oauth.OAuthConsumer(api_key, secret_key)
46         self.sig_method = oauth.OAuthSignatureMethod_HMAC_SHA1()
47
48
49     # -----------------------------------------------------------------------
50     def authenticate(self, need_auth, block_for_auth):
51         need_auth()
52         token = self._get_request_token()
53         url = self._get_authorize_url(token)
54         verifier = block_for_auth(url)
55         self._verify_verifier(token, verifier)
56     
57     
58     # -----------------------------------------------------------------------
59     def get_friends(self):
60         """ Returns a Friend object for each LinkedIn connection."""
61         
62         xml = self._make_api_request(self.LI_CONN_API_URL)
63         dom = parseString(xml)
64         friends = self._parse_dom(dom)
65
66         return friends
67         
68
69     # -----------------------------------------------------------------------
70     def get_friend_details(self, url, header_value):
71         oauth_request = oauth.OAuthRequest.from_consumer_and_token(self.consumer, token=self.access_token, http_url=url)
72         oauth_request.sign_request(self.sig_method, self.consumer, self.access_token)
73
74         headers = oauth_request.to_header()
75         headers[u'x-li-auth-token'] = header_value
76         connection = httplib.HTTPConnection("api.linkedin.com")
77         connection.request(oauth_request.http_method, url, headers=headers)
78         data = connection.getresponse().read()
79         return data
80     
81
82     # -----------------------------------------------------------------------
83     def _make_api_request(self, url):
84         oauth_request = oauth.OAuthRequest.from_consumer_and_token(self.consumer, token=self.access_token, http_url=url)
85         oauth_request.sign_request(self.sig_method, self.consumer, self.access_token)
86         connection = httplib.HTTPSConnection(self.LI_SERVER)
87         try:
88             connection.request(oauth_request.http_method, url, headers=oauth_request.to_header())
89             xml = connection.getresponse().read()
90             return xml
91         except:
92             raise Exception("Failed to contact LinkedIn at " + url)
93
94
95     # -----------------------------------------------------------------------
96     def _parse_dom(self, dom):
97         def get_first_tag(node, tagName):
98             tags = node.getElementsByTagName(tagName)
99             if tags and len(tags) > 0:
100                 return tags[0]
101         
102         def extract(node, tagName):
103             tag = get_first_tag(node, tagName)
104             if tag:
105                 return tag.firstChild.nodeValue
106             
107         def extract_public_url(node):
108             tag = get_first_tag(node, 'site-standard-profile-request')
109             if tag:
110                 url = extract(tag, 'url')
111                 return url.replace("&amp;", "&")
112         
113         # look for errors
114         errors = dom.getElementsByTagName('error')
115         if (len(errors) > 0):
116             details = ""
117             try:
118                 details = " (" + extract(errors[0], "message") + ")"
119             except:
120                 pass
121             raise Exception("LinkedIn communication errors detected" + details)
122         
123         friends = []
124         people = dom.getElementsByTagName('person')
125         for p in people:
126             try:
127                 fn = extract(p, 'first-name')
128                 ln = extract(p, 'last-name')
129                 photo_url = extract(p, 'picture-url')
130                 id = extract(p, 'id')
131                 public_url = extract_public_url(p)
132
133                 name = fn + " " + ln
134                 friend = Friend(name)
135                 friend.add_url(public_url)
136                 if photo_url:
137                     friend.set_photo_url(photo_url)
138                 
139                 friends.append(friend)
140
141             except:
142                 pass
143
144         return friends
145
146     # -----------------------------------------------------------------------
147     def _get_request_token(self):
148         """Get a request token from LinkedIn"""
149         
150         oauth_consumer_key = self.api_key
151         oauth_request = oauth.OAuthRequest.from_consumer_and_token(self.consumer, callback="oob", http_url=self.REQUEST_TOKEN_URL)
152         oauth_request.sign_request(self.sig_method, self.consumer, None)
153
154         connection = httplib.HTTPSConnection(self.LI_SERVER)
155         connection.request(oauth_request.http_method, self.REQUEST_TOKEN_URL, headers=oauth_request.to_header())
156         response = connection.getresponse().read()
157         
158         try:
159             token = oauth.OAuthToken.from_string(response)
160         except Exception, e:
161             import traceback
162             traceback.print_exc()
163             print response
164             raise Exception("Authorization failure - failed to get request token")
165         return token
166
167     
168     # -----------------------------------------------------------------------
169     def _get_authorize_url(self, token):
170         """The URL that the user should browse to, in order to authorize the 
171            application's request to access data"""
172         
173         oauth_request = oauth.OAuthRequest.from_token_and_callback(token=token, http_url=self.AUTHORIZE_URL)
174         return oauth_request.to_url()
175
176
177     # -----------------------------------------------------------------------
178     def _get_access_token(self, token, verifier):
179         """If the verifier (which was displayed in the browser window) is 
180            valid, then an access token is returned which should be used to 
181            access data on the service."""
182         
183         oauth_request = oauth.OAuthRequest.from_consumer_and_token(self.consumer, token=token, verifier=verifier, http_url=self.ACCESS_TOKEN_URL)
184         oauth_request.sign_request(self.sig_method, self.consumer, token)
185
186         connection = httplib.HTTPSConnection(self.LI_SERVER)
187         connection.request(oauth_request.http_method, self.ACCESS_TOKEN_URL, headers=oauth_request.to_header()) 
188         response = connection.getresponse()
189         token_str = response.read()
190         if 'oauth_problem' in token_str:
191             raise Exception("Authorization failure - failed to get access token (" + token_str + ")")
192         self._store_access_token_in_gconf(token_str)
193         return oauth.OAuthToken.from_string(token_str)
194
195
196     # -----------------------------------------------------------------------
197     def _verify_verifier(self, request_token, verifier):
198         try:
199             self.access_token = self._get_access_token(request_token, verifier)
200             xml = self._make_api_request(self.LI_PROFILE_API_URL)
201             dom = parseString(xml)
202             friends = self._parse_dom(dom)
203             self._gc.set_string(LinkedInApi.GCONF_USER, friends[0].get_name())
204         except Exception, e:
205             import traceback
206             traceback.print_exc()
207             raise Exception("LinkedIn authorization failed, try again")
208
209
210     # -----------------------------------------------------------------------
211     def _store_access_token_in_gconf(self, token_str):
212         if "oauth_problem" in token_str:
213             raise Exception("Authorization failure - access token reported OAuth problem")
214         
215         self._gc.set_string(LinkedInApi.GCONF_ACCESS_TOKEN, token_str)
216
217         
218     # -----------------------------------------------------------------------
219     def get_access_token_from_gconf(self):
220         """Returns an oauth.OAuthToken, or None if the gconf value is empty"""
221         
222         token_str = self._gc.get_string(LinkedInApi.GCONF_ACCESS_TOKEN)
223         if not token_str:
224             return None
225         return oauth.OAuthToken.from_string(token_str)
226
227
228     # -----------------------------------------------------------------------
229     def remove_access_token_from_gconf(self):
230         """Remove the oauth.OAuthToken, if any."""
231         
232         self._gc.unset(LinkedInApi.GCONF_ACCESS_TOKEN)
233         self._gc.unset(LinkedInApi.GCONF_USER)