4 from oauth import oauth
5 from xml.dom.minidom import parseString
6 from org.maemo.hermes.engine.phonenumber import PhoneNumber
7 from org.maemo.hermes.engine.friend import Friend
10 """LinkedIn API for Hermes.
12 Copyright (c) Fredrik Wendt <fredrik@wendt.se> 2010.
13 Copyright (c) Andrew Flegg <andrew@bleb.org> 2010.
14 Released under the Artistic Licence."""
16 GCONF_API_KEY = '/apps/maemo/hermes/linkedin_key'
17 GCONF_API_SECRET = '/apps/maemo/hermes/linkedin_secret'
18 GCONF_ACCESS_TOKEN = '/apps/maemo/hermes/linkedin_access_token'
19 GCONF_USER = '/apps/maemo/hermes/linkedin_user'
21 LI_SERVER = "api.linkedin.com"
22 LI_API_URL = "https://api.linkedin.com"
23 LI_CONN_API_URL = LI_API_URL + "/v1/people/~/connections:(id,first-name,last-name,picture-url,site-standard-profile-request:(url),date-of-birth,main-address,location:(country:(code)),phone-numbers,member-url-resources)"
24 LI_PROFILE_API_URL = LI_API_URL + "/v1/people/~"
26 REQUEST_TOKEN_URL = LI_API_URL + "/uas/oauth/requestToken"
27 AUTHORIZE_URL = LI_API_URL + "/uas/oauth/authorize"
28 ACCESS_TOKEN_URL = LI_API_URL + "/uas/oauth/accessToken"
31 # -----------------------------------------------------------------------
32 def __init__(self, gconf=None):
33 """Initialize the LinkedIn service, finding LinkedIn API keys in gconf and
34 having a gui_callback available."""
36 if gconf: self._gc = gconf
37 else: self._gc = gnome.gconf.client_get_default()
39 api_key = self._gc.get_string(LinkedInApi.GCONF_API_KEY)
40 secret_key = self._gc.get_string(LinkedInApi.GCONF_API_SECRET)
41 self.api_key = api_key
43 if api_key is None or secret_key is None:
44 raise Exception('No LinkedIn application keys found. Installation error.')
46 self.access_token = self.get_access_token_from_gconf()
48 self.consumer = oauth.OAuthConsumer(api_key, secret_key)
49 self.sig_method = oauth.OAuthSignatureMethod_HMAC_SHA1()
52 # -----------------------------------------------------------------------
53 def authenticate(self, need_auth, block_for_auth):
55 token = self._get_request_token()
56 url = self._get_authorize_url(token)
57 verifier = block_for_auth(url)
58 self._verify_verifier(token, verifier)
61 # -----------------------------------------------------------------------
62 def get_friends(self):
63 """ Returns a Friend object for each LinkedIn connection."""
65 xml = self._make_api_request(self.LI_CONN_API_URL)
66 dom = parseString(xml)
67 friends = self._parse_dom(dom)
72 # -----------------------------------------------------------------------
73 def get_friend_details(self, url, header_value):
74 oauth_request = oauth.OAuthRequest.from_consumer_and_token(self.consumer, token=self.access_token, http_url=url)
75 oauth_request.sign_request(self.sig_method, self.consumer, self.access_token)
77 headers = oauth_request.to_header()
78 headers[u'x-li-auth-token'] = header_value
79 connection = httplib.HTTPConnection("api.linkedin.com")
80 connection.request(oauth_request.http_method, url, headers=headers)
81 data = connection.getresponse().read()
85 # -----------------------------------------------------------------------
86 def _make_api_request(self, url):
87 oauth_request = oauth.OAuthRequest.from_consumer_and_token(self.consumer, token=self.access_token, http_url=url)
88 oauth_request.sign_request(self.sig_method, self.consumer, self.access_token)
89 connection = httplib.HTTPSConnection(self.LI_SERVER)
91 connection.request(oauth_request.http_method, url, headers=oauth_request.to_header())
92 xml = connection.getresponse().read()
95 raise Exception("Failed to contact LinkedIn at " + url)
98 # -----------------------------------------------------------------------
99 def _parse_dom(self, dom):
100 def get_first_tag(node, tagName):
102 tags = node.getElementsByTagName(tagName)
103 if tags and len(tags) > 0:
106 def extract(node, tagName):
107 tag = get_first_tag(node, tagName)
109 return tag.firstChild.nodeValue
111 def extract_public_url(node):
112 tag = get_first_tag(node, 'site-standard-profile-request')
114 url = extract(tag, 'url').replace("&", "&")
115 return re.sub('[?&](auth|trk)\w*=[^&]*', '', url)
117 def extract_phone_numbers(node):
118 country = extract(get_first_tag(node, 'country'), 'code')
119 tag = get_first_tag(node, 'phone-numbers')
121 for element in tag.childNodes:
122 if element.nodeName != 'phone-number':
125 phone_type = extract(element, 'phone-type')
126 device = phone_type == 'mobile' and phone_type or None
127 type = phone_type in set(['home', 'work']) and phone_type or None
129 number = PhoneNumber(extract(element, 'phone-number'), device = device, type = type, country = country)
130 numbers.append(number)
134 errors = dom.getElementsByTagName('error')
135 if (len(errors) > 0):
138 details = " (" + extract(errors[0], "message") + ")"
141 raise Exception("LinkedIn communication errors detected" + details)
144 people = dom.getElementsByTagName('person')
147 fn = extract(p, 'first-name')
148 ln = extract(p, 'last-name')
149 photo_url = extract(p, 'picture-url')
150 id = extract(p, 'id')
151 public_url = extract_public_url(p)
154 friend = Friend(name)
155 friend.add_url(public_url)
157 friend.set_photo_url(photo_url)
159 for number in extract_phone_numbers(p):
160 friend.add_phone(number)
162 friends.append(friend)
169 # -----------------------------------------------------------------------
170 def _get_request_token(self):
171 """Get a request token from LinkedIn"""
173 oauth_consumer_key = self.api_key
174 oauth_request = oauth.OAuthRequest.from_consumer_and_token(self.consumer, callback="oob", http_url=self.REQUEST_TOKEN_URL)
175 oauth_request.sign_request(self.sig_method, self.consumer, None)
177 connection = httplib.HTTPSConnection(self.LI_SERVER)
178 connection.request(oauth_request.http_method, self.REQUEST_TOKEN_URL, headers=oauth_request.to_header())
179 response = connection.getresponse().read()
182 token = oauth.OAuthToken.from_string(response)
185 traceback.print_exc()
187 raise Exception("Authorization failure - failed to get request token")
191 # -----------------------------------------------------------------------
192 def _get_authorize_url(self, token):
193 """The URL that the user should browse to, in order to authorize the
194 application's request to access data"""
196 oauth_request = oauth.OAuthRequest.from_token_and_callback(token=token, http_url=self.AUTHORIZE_URL)
197 return oauth_request.to_url()
200 # -----------------------------------------------------------------------
201 def _get_access_token(self, token, verifier):
202 """If the verifier (which was displayed in the browser window) is
203 valid, then an access token is returned which should be used to
204 access data on the service."""
206 oauth_request = oauth.OAuthRequest.from_consumer_and_token(self.consumer, token=token, verifier=verifier, http_url=self.ACCESS_TOKEN_URL)
207 oauth_request.sign_request(self.sig_method, self.consumer, token)
209 connection = httplib.HTTPSConnection(self.LI_SERVER)
210 connection.request(oauth_request.http_method, self.ACCESS_TOKEN_URL, headers=oauth_request.to_header())
211 response = connection.getresponse()
212 token_str = response.read()
213 if 'oauth_problem' in token_str:
214 raise Exception("Authorization failure - failed to get access token (" + token_str + ")")
215 self._store_access_token_in_gconf(token_str)
216 return oauth.OAuthToken.from_string(token_str)
219 # -----------------------------------------------------------------------
220 def _verify_verifier(self, request_token, verifier):
222 self.access_token = self._get_access_token(request_token, verifier)
223 xml = self._make_api_request(self.LI_PROFILE_API_URL)
224 dom = parseString(xml)
225 friends = self._parse_dom(dom)
226 self._gc.set_string(LinkedInApi.GCONF_USER, friends[0].get_name())
229 traceback.print_exc()
230 raise Exception("LinkedIn authorization failed, try again")
233 # -----------------------------------------------------------------------
234 def _store_access_token_in_gconf(self, token_str):
235 if "oauth_problem" in token_str:
236 raise Exception("Authorization failure - access token reported OAuth problem")
238 self._gc.set_string(LinkedInApi.GCONF_ACCESS_TOKEN, token_str)
241 # -----------------------------------------------------------------------
242 def get_access_token_from_gconf(self):
243 """Returns an oauth.OAuthToken, or None if the gconf value is empty"""
245 token_str = self._gc.get_string(LinkedInApi.GCONF_ACCESS_TOKEN)
248 return oauth.OAuthToken.from_string(token_str)
251 # -----------------------------------------------------------------------
252 def remove_access_token_from_gconf(self):
253 """Remove the oauth.OAuthToken, if any."""
255 self._gc.unset(LinkedInApi.GCONF_ACCESS_TOKEN)
256 self._gc.unset(LinkedInApi.GCONF_USER)