4 from oauth import oauth
5 from xml.dom.minidom import parseString
6 from org.maemo.hermes.engine.friend import Friend
9 """LinkedIn API for Hermes.
11 Copyright (c) Fredrik Wendt <fredrik@wendt.se> 2010.
12 Released under the Artistic Licence."""
14 GCONF_API_KEY = '/apps/maemo/hermes/linkedin_key'
15 GCONF_API_SECRET = '/apps/maemo/hermes/linkedin_secret'
16 GCONF_ACCESS_TOKEN = '/apps/maemo/hermes/linkedin_access_token'
17 GCONF_USER = '/apps/maemo/hermes/linkedin_user'
19 LI_SERVER = "api.linkedin.com"
20 LI_API_URL = "https://api.linkedin.com"
21 LI_CONN_API_URL = LI_API_URL + "/v1/people/~/connections"
22 LI_PROFILE_API_URL = LI_API_URL + "/v1/people/~"
24 REQUEST_TOKEN_URL = LI_API_URL + "/uas/oauth/requestToken"
25 AUTHORIZE_URL = LI_API_URL + "/uas/oauth/authorize"
26 ACCESS_TOKEN_URL = LI_API_URL + "/uas/oauth/accessToken"
29 # -----------------------------------------------------------------------
30 def __init__(self, gconf=None):
31 """Initialize the LinkedIn service, finding LinkedIn API keys in gconf and
32 having a gui_callback available."""
34 if gconf: self._gc = gconf
35 else: self._gc = gnome.gconf.client_get_default()
37 api_key = self._gc.get_string(LinkedInApi.GCONF_API_KEY)
38 secret_key = self._gc.get_string(LinkedInApi.GCONF_API_SECRET)
39 self.api_key = api_key
41 if api_key is None or secret_key is None:
42 raise Exception('No LinkedIn application keys found. Installation error.')
44 self.access_token = self.get_access_token_from_gconf()
46 self.consumer = oauth.OAuthConsumer(api_key, secret_key)
47 self.sig_method = oauth.OAuthSignatureMethod_HMAC_SHA1()
50 # -----------------------------------------------------------------------
51 def authenticate(self, need_auth, block_for_auth):
53 token = self._get_request_token()
54 url = self._get_authorize_url(token)
55 verifier = block_for_auth(url)
56 self._verify_verifier(token, verifier)
59 # -----------------------------------------------------------------------
60 def get_friends(self):
61 """ Returns a Friend object for each LinkedIn connection."""
63 xml = self._make_api_request(self.LI_CONN_API_URL)
64 dom = parseString(xml)
65 friends = self._parse_dom(dom)
70 # -----------------------------------------------------------------------
71 def get_friend_details(self, url, header_value):
72 oauth_request = oauth.OAuthRequest.from_consumer_and_token(self.consumer, token=self.access_token, http_url=url)
73 oauth_request.sign_request(self.sig_method, self.consumer, self.access_token)
75 headers = oauth_request.to_header()
76 headers[u'x-li-auth-token'] = header_value
77 connection = httplib.HTTPConnection("api.linkedin.com")
78 connection.request(oauth_request.http_method, url, headers=headers)
79 data = connection.getresponse().read()
83 # -----------------------------------------------------------------------
84 def _make_api_request(self, url):
85 oauth_request = oauth.OAuthRequest.from_consumer_and_token(self.consumer, token=self.access_token, http_url=url)
86 oauth_request.sign_request(self.sig_method, self.consumer, self.access_token)
87 connection = httplib.HTTPSConnection(self.LI_SERVER)
89 connection.request(oauth_request.http_method, url, headers=oauth_request.to_header())
90 xml = connection.getresponse().read()
93 raise Exception("Failed to contact LinkedIn at " + url)
96 # -----------------------------------------------------------------------
97 def _parse_dom(self, dom):
98 def get_first_tag(node, tagName):
99 tags = node.getElementsByTagName(tagName)
100 if tags and len(tags) > 0:
103 def extract(node, tagName):
104 tag = get_first_tag(node, tagName)
106 return tag.firstChild.nodeValue
108 def extract_public_url(node):
109 tag = get_first_tag(node, 'site-standard-profile-request')
111 url = extract(tag, 'url').replace("&", "&")
112 return re.sub('[?&](auth|trk)\w*=[^&]*', '', url)
115 errors = dom.getElementsByTagName('error')
116 if (len(errors) > 0):
119 details = " (" + extract(errors[0], "message") + ")"
122 raise Exception("LinkedIn communication errors detected" + details)
125 people = dom.getElementsByTagName('person')
128 fn = extract(p, 'first-name')
129 ln = extract(p, 'last-name')
130 photo_url = extract(p, 'picture-url')
131 id = extract(p, 'id')
132 public_url = extract_public_url(p)
135 friend = Friend(name)
136 friend.add_url(public_url)
138 friend.set_photo_url(photo_url)
140 friends.append(friend)
147 # -----------------------------------------------------------------------
148 def _get_request_token(self):
149 """Get a request token from LinkedIn"""
151 oauth_consumer_key = self.api_key
152 oauth_request = oauth.OAuthRequest.from_consumer_and_token(self.consumer, callback="oob", http_url=self.REQUEST_TOKEN_URL)
153 oauth_request.sign_request(self.sig_method, self.consumer, None)
155 connection = httplib.HTTPSConnection(self.LI_SERVER)
156 connection.request(oauth_request.http_method, self.REQUEST_TOKEN_URL, headers=oauth_request.to_header())
157 response = connection.getresponse().read()
160 token = oauth.OAuthToken.from_string(response)
163 traceback.print_exc()
165 raise Exception("Authorization failure - failed to get request token")
169 # -----------------------------------------------------------------------
170 def _get_authorize_url(self, token):
171 """The URL that the user should browse to, in order to authorize the
172 application's request to access data"""
174 oauth_request = oauth.OAuthRequest.from_token_and_callback(token=token, http_url=self.AUTHORIZE_URL)
175 return oauth_request.to_url()
178 # -----------------------------------------------------------------------
179 def _get_access_token(self, token, verifier):
180 """If the verifier (which was displayed in the browser window) is
181 valid, then an access token is returned which should be used to
182 access data on the service."""
184 oauth_request = oauth.OAuthRequest.from_consumer_and_token(self.consumer, token=token, verifier=verifier, http_url=self.ACCESS_TOKEN_URL)
185 oauth_request.sign_request(self.sig_method, self.consumer, token)
187 connection = httplib.HTTPSConnection(self.LI_SERVER)
188 connection.request(oauth_request.http_method, self.ACCESS_TOKEN_URL, headers=oauth_request.to_header())
189 response = connection.getresponse()
190 token_str = response.read()
191 if 'oauth_problem' in token_str:
192 raise Exception("Authorization failure - failed to get access token (" + token_str + ")")
193 self._store_access_token_in_gconf(token_str)
194 return oauth.OAuthToken.from_string(token_str)
197 # -----------------------------------------------------------------------
198 def _verify_verifier(self, request_token, verifier):
200 self.access_token = self._get_access_token(request_token, verifier)
201 xml = self._make_api_request(self.LI_PROFILE_API_URL)
202 dom = parseString(xml)
203 friends = self._parse_dom(dom)
204 self._gc.set_string(LinkedInApi.GCONF_USER, friends[0].get_name())
207 traceback.print_exc()
208 raise Exception("LinkedIn authorization failed, try again")
211 # -----------------------------------------------------------------------
212 def _store_access_token_in_gconf(self, token_str):
213 if "oauth_problem" in token_str:
214 raise Exception("Authorization failure - access token reported OAuth problem")
216 self._gc.set_string(LinkedInApi.GCONF_ACCESS_TOKEN, token_str)
219 # -----------------------------------------------------------------------
220 def get_access_token_from_gconf(self):
221 """Returns an oauth.OAuthToken, or None if the gconf value is empty"""
223 token_str = self._gc.get_string(LinkedInApi.GCONF_ACCESS_TOKEN)
226 return oauth.OAuthToken.from_string(token_str)
229 # -----------------------------------------------------------------------
230 def remove_access_token_from_gconf(self):
231 """Remove the oauth.OAuthToken, if any."""
233 self._gc.unset(LinkedInApi.GCONF_ACCESS_TOKEN)
234 self._gc.unset(LinkedInApi.GCONF_USER)