Permalink
Browse files

Merge branch 'linkedin'

  • Loading branch information...
2 parents d68c961 + 00b0256 commit de3de58f8a16ec8b32aae9cf2d2af4c4041b88fd @brosner brosner committed Apr 13, 2010
View
168 contacts_import/oauth_consumer.py
@@ -0,0 +1,168 @@
+import httplib2
+import logging
+import socket
+
+from django.conf import settings
+from django.core.exceptions import ImproperlyConfigured
+from django.core.urlresolvers import reverse
+from django.utils import simplejson as json
+
+import oauth2 as oauth
+
+from contacts_import.utils.anyetree import etree
+
+
+logger = logging.getLogger("oauth_consumer")
+
+
+class ServiceFail(Exception):
+ pass
+
+
+class oAuthConsumer(object):
+
+ def __init__(self, service):
+ self.service = service
+ self.signature_method = oauth.SignatureMethod_HMAC_SHA1()
+ self.consumer = oauth.Consumer(self.key, self.secret)
+
+ @property
+ def key(self):
+ return self._obtain_setting("keys", "KEY")
+
+ @property
+ def secret(self):
+ return self._obtain_setting("keys", "SECRET")
+
+ @property
+ def request_token_url(self):
+ return self._obtain_setting("endpoints", "request_token")
+
+ @property
+ def access_token_url(self):
+ return self._obtain_setting("endpoints", "access_token")
+
+ @property
+ def authorize_url(self):
+ return self._obtain_setting("endpoints", "authorize")
+
+ def _obtain_setting(self, k1, k2):
+ name = "OAUTH_CONSUMER_SETTINGS"
+ service = self.service
+ try:
+ return getattr(settings, name)[service][k1][k2]
+ except AttributeError:
+ raise ImproperlyConfigured("%s must be defined in settings" % (name,))
+ except KeyError, e:
+ key = e.args[0]
+ if key == service:
+ raise ImproperlyConfigured("%s must contain '%s'" % (name, service))
+ elif key == k1:
+ raise ImproperlyConfigured("%s must contain '%s' for '%s'" % (name, k1, service))
+ elif key == k2:
+ raise ImproperlyConfigured("%s must contain '%s' for '%s' in '%s'" % (name, k2, k1, service))
+ else:
+ raise
+
+ def unauthorized_token(self):
+ if not hasattr(self, "_unauthorized_token"):
+ self._unauthorized_token = self.fetch_unauthorized_token()
+ return self._unauthorized_token
+
+ def fetch_unauthorized_token(self):
+ # @@@ fixme
+ base_url = "http://contacts-import.pinaxproject.com"
+ callback_url = reverse("oauth_callback", kwargs={"service": self.service})
+ request = oauth.Request.from_consumer_and_token(self.consumer,
+ http_url = self.request_token_url,
+ http_method = "POST",
+ parameters = {
+ "oauth_callback": "%s%s" % (base_url, callback_url),
+ }
+ )
+ request.sign_request(self.signature_method, self.consumer, None)
+ try:
+ return oauth.Token.from_string(self._oauth_response(request))
+ except KeyError, e:
+ if e.args[0] == "oauth_token":
+ raise ServiceFail()
+ raise
+
+ def authorized_token(self, token, verifier=None):
+ parameters = {}
+ if verifier:
+ parameters.update({
+ "oauth_verifier": verifier,
+ })
+ request = oauth.Request.from_consumer_and_token(self.consumer,
+ token = token,
+ http_url = self.access_token_url,
+ http_method = "POST",
+ parameters = parameters,
+ )
+ request.sign_request(self.signature_method, self.consumer, token)
+ try:
+ return oauth.Token.from_string(self._oauth_response(request))
+ except KeyError:
+ raise ServiceFail()
+
+ def check_token(self, unauth_token, parameters):
+ token = oauth.Token.from_string(unauth_token)
+ if token.key == parameters.get("oauth_token", "no_token"):
+ verifier = parameters.get("oauth_verifier")
+ return self.authorized_token(token, verifier)
+ else:
+ return None
+
+ def authorization_url(self, token):
+ request = oauth.Request.from_consumer_and_token(
+ self.consumer,
+ token = token,
+ http_url = self.authorize_url,
+ )
+ request.sign_request(self.signature_method, self.consumer, token)
+ return request.to_url()
+
+ def make_api_call(self, kind, url, token, http_method="GET", **kwargs):
+ if isinstance(token, basestring):
+ token = oauth.Token.from_string(token)
+ response = self._oauth_response(
+ self._oauth_request(url, token,
+ http_method = http_method,
+ params = kwargs,
+ )
+ )
+ if not response:
+ raise ServiceFail()
+ logger.debug(repr(response))
+ if kind == "json":
+ try:
+ return json.loads(response)
+ except ValueError:
+ # @@@ might be better to return a uniform cannot parse
+ # exception and let caller determine if it is service fail
+ raise ServiceFail()
+ elif kind == "xml":
+ return etree.fromstring(response)
+ else:
+ raise Exception("unsupported API kind")
+
+ def _oauth_request(self, url, token, http_method="GET", params=None):
+ request = oauth.Request.from_consumer_and_token(self.consumer,
+ token = token,
+ http_url = url,
+ parameters = params,
+ http_method = http_method,
+ )
+ request.sign_request(self.signature_method, self.consumer, token)
+ return request
+
+ def _oauth_response(self, request):
+ # @@@ not sure if this will work everywhere. need to explore more.
+ http = httplib2.Http()
+ headers = {}
+ headers.update(request.to_header())
+ ret = http.request(request.url, request.method, headers=headers)
+ response, content = ret
+ logger.debug(repr(ret))
+ return content
View
2 contacts_import/urls.py
@@ -9,4 +9,6 @@
url(r"^bbauth/login/$", "bbauth.views.login", {
"redirect_to": "/contacts/import_contacts/",
}, name="bbauth_login"),
+ url(r"oauth/login/(?P<service>\w+)/", "contacts_import.views.oauth_login", name="oauth_login"),
+ url(r"oauth/callback/(?P<service>\w+)/", "contacts_import.views.oauth_callback", name="oauth_callback"),
)
View
0 contacts_import/utils/__init__.py
No changes.
View
38 contacts_import/utils/anyetree.py
@@ -0,0 +1,38 @@
+"""
+Get an Etree library. Usage::
+
+ >>> from anyetree import etree
+
+Returns some etree library. Looks for (in order of decreasing preference):
+
+ * ``lxml.etree`` (http://cheeseshop.python.org/pypi/lxml/)
+ * ``xml.etree.cElementTree`` (built into Python 2.5)
+ * ``cElementTree`` (http://effbot.org/zone/celementtree.htm)
+ * ``xml.etree.ElementTree`` (built into Python 2.5)
+ * ``elementree.ElementTree (http://effbot.org/zone/element-index.htm)
+"""
+
+
+__all__ = ["etree"]
+
+
+SEARCH_PATHS = [
+ "lxml.etree",
+ "xml.etree.cElementTree",
+ "cElementTree",
+ "xml.etree.ElementTree",
+ "elementtree.ElementTree",
+]
+
+etree = None
+
+for name in SEARCH_PATHS:
+ try:
+ # @@@ move to import_module
+ etree = __import__(name, {}, {}, [""])
+ break
+ except ImportError:
+ continue
+
+if etree is None:
+ raise ImportError("No suitable ElementTree implementation found.")
View
34 contacts_import/views.py
@@ -1,6 +1,7 @@
+from django.conf import settings
from django.core.paginator import Paginator
from django.core.urlresolvers import reverse
-from django.http import HttpResponseRedirect
+from django.http import HttpResponse, HttpResponseRedirect
from django.shortcuts import render_to_response
from django.template import RequestContext
from django.utils.translation import ugettext as _
@@ -12,6 +13,7 @@
from contacts_import.forms import VcardImportForm
from contacts_import.backends.importers import GoogleImporter, YahooImporter
from contacts_import.backends.runners import SynchronousRunner, AsyncRunner
+from contacts_import.oauth_consumer import oAuthConsumer
GOOGLE_CONTACTS_URI = "http://www.google.com/m8/feeds/"
@@ -56,6 +58,12 @@ def import_contacts(request, runner_class=AsyncRunner):
results = runner_class(GoogleImporter, user=request.user,
authsub_token=authsub_token).import_contacts()
return _import_success(request, results)
+ elif request.POST["action"] == "import_linkedin":
+ linkedin_token = request.session.pop("linkedin_token", None)
+ if linkedin_token:
+ consumer = oAuthConsumer("linkedin")
+ ret = consumer.make_api_call("xml", "https://api.linkedin.com/v1/people/~", linkedin_token)
+ return HttpResponse(repr(ret))
else:
form = VcardImportForm()
@@ -66,6 +74,7 @@ def import_contacts(request, runner_class=AsyncRunner):
"form": form,
"bbauth_token": request.session.get("bbauth_token"),
"authsub_token": request.session.get("authsub_token"),
+ "linkedin_token": request.session.get("linkedin_token"),
"page": page,
"task_id": request.session.pop("import_contacts_task_id", None),
}, context_instance=RequestContext(request))
@@ -83,3 +92,26 @@ def authsub_login(request, redirect_to=None):
request.session["authsub_token"] = request.GET["token"]
return HttpResponseRedirect(redirect_to)
return HttpResponseRedirect(_authsub_url(request.build_absolute_uri()))
+
+
+def oauth_login(request, service):
+ consumer = oAuthConsumer(service)
+ token = consumer.unauthorized_token()
+ request.session["%s_unauth_token" % service] = token.to_string()
+ return HttpResponseRedirect(consumer.authorization_url(token))
+
+
+def oauth_callback(request, service):
+ ctx = RequestContext(request)
+ consumer = oAuthConsumer(service)
+ unauth_token = request.session.get("%s_unauth_token" % service, None)
+ if unauth_token is None:
+ ctx.update({"error": "token_missing"})
+ else:
+ auth_token = consumer.check_token(unauth_token, request.GET)
+ if auth_token:
+ request.session["%s_token" % service] = str(auth_token)
+ return HttpResponseRedirect(reverse("import_contacts"))
+ else:
+ ctx.update({"error": "token_mismatch"})
+ return render_to_response("oauth_error.html", ctx)
View
2 requirements.txt
@@ -1,5 +1,5 @@
celery==0.8.2
-httplib2==0.5.0
+httplib2==0.6.0
gdata==1.3.3
ybrowserauth==1.2
vobject==0.8.1c

0 comments on commit de3de58

Please sign in to comment.