diff --git a/lms/djangoapps/linkedin/management/commands/api.py b/lms/djangoapps/linkedin/management/commands/api.py new file mode 100644 index 0000000000..5b0f46a209 --- /dev/null +++ b/lms/djangoapps/linkedin/management/commands/api.py @@ -0,0 +1,99 @@ +import hashlib +import json +import urllib2 +import urlparse +import uuid + +from django.conf import settings +from django.core.management.base import CommandError + +from ...models import LinkedInToken + +class LinkedinAPI(object): + """ + Encapsulates the LinkedIn API. + """ + def __init__(self): + config = getattr(settings, "LINKEDIN_API", None) + if not config: + raise CommandError("LINKEDIN_API is not configured") + self.config = config + + try: + self.tokens = LinkedInToken.objects.get() + except LinkedInToken.DoesNotExist: + self.tokens = None + + self.state = str(uuid.uuid4()) + + def http_error(error, message): + print "!!ERROR!!" + print error + print error.read() + raise CommandError(message) + + def authorization_url(self): + config = self.config + return ("https://www.linkedin.com/uas/oauth2/authorization" + "?response_type=code" + "&client_id=%s&state=%s&redirect_uri=%s" % ( + config['CLIENT_ID'], self.state, config['REDIRECT_URI'])) + + def get_authorization_code(self, redirect): + query = urlparse.parse_qs(urlparse.urlparse(redirect).query) + assert query['state'][0] == self.state, (query['state'][0], self.state) + return query['code'][0] + + def get_access_token(self, code): + config = self.config + url = ("https://www.linkedin.com/uas/oauth2/accessToken" + "?grant_type=authorization_code" + "&code=%s&redirect_uri=%s&client_id=%s&client_secret=%s" % ( + code, config['REDIRECT_URI'], config['CLIENT_ID'], + config['CLIENT_SECRET'])) + + try: + response = urllib2.urlopen(url).read() + except urllib2.HTTPError, error: + self.http_error(error, "Unable to retrieve access token") + + access_token = json.loads(response)['access_token'] + try: + tokens = LinkedInToken.objects.get() + tokens.access_token = access_token + tokens.authorization_code = code + except LinkedInToken.DoesNotExist: + tokens = LinkedInToken(access_token=access_token) + tokens.save() + self.tokens = tokens + + return access_token + + def batch(self, emails): + """ + Get the LinkedIn status for a batch of emails. + """ + if self.tokens is None: + raise CommandError( + "You must log in to LinkedIn in order to use this script. " + "Please use the 'login' command to log in to LinkedIn.") + + def md5(email): + "Compute md5 hash for an email address." + hash = hashlib.md5() + hash.update(email) + return hash.hexdigest() + + hashes = ','.join(("email-hash=" + md5(email) for email in emails)) + url = "https://api.linkedin.com/v1/people::(%s):(id)" % hashes + url += "?oauth2_access_token=%s" % self.tokens.access_token + try: + response = urllib2.urlopen(url).read() + except urllib2.HTTPError, error: + print "!!ERROR!!" + print error + print error.read() + + raise CommandError("Unable to access People API") + + return (True for email in emails) diff --git a/lms/djangoapps/linkedin/management/commands/findusers.py b/lms/djangoapps/linkedin/management/commands/findusers.py index bcd4b13c1a..5311bfc581 100644 --- a/lms/djangoapps/linkedin/management/commands/findusers.py +++ b/lms/djangoapps/linkedin/management/commands/findusers.py @@ -13,6 +13,7 @@ from django.utils import timezone from optparse import make_option from ...models import LinkedIn +from .api import LinkedinAPI FRIDAY = 4 @@ -112,15 +113,3 @@ class Command(BaseCommand): do_batch(batch) except StopIteration: pass - - -class LinkedinAPI(object): - """ - Encapsulates the LinkedIn API. - """ - - def batch(self, emails): - """ - Get the LinkedIn status for a batch of emails. - """ - return (True for email in emails) diff --git a/lms/djangoapps/linkedin/management/commands/login.py b/lms/djangoapps/linkedin/management/commands/login.py index 9a0f525ee3..04c16696e4 100644 --- a/lms/djangoapps/linkedin/management/commands/login.py +++ b/lms/djangoapps/linkedin/management/commands/login.py @@ -1,15 +1,9 @@ """ Log into LinkedIn API. """ -import json -import urllib2 -import urlparse -import uuid +from django.core.management.base import BaseCommand -from django.conf import settings -from django.core.management.base import BaseCommand, CommandError - -from ...models import LinkedInTokens +from .api import LinkedinAPI class Command(BaseCommand): @@ -25,48 +19,13 @@ class Command(BaseCommand): def handle(self, *args, **options): """ """ - api = getattr(settings, "LINKEDIN_API", None) - if not api: - raise CommandError("LINKEDIN_API is not configured") - - state = str(uuid.uuid4()) - url = ("https://www.linkedin.com/uas/oauth2/authorization" - "?response_type=code" - "&client_id=%s&state=%s&redirect_uri=%s" % ( - api['CLIENT_ID'], state, api['REDIRECT_URI'])) - + api = LinkedinAPI() print "Let's log into your LinkedIn account." print "Start by visiting this url:" - print url + print api.authorization_url() print print "Within 30 seconds of logging in, enter the full URL of the " print "webpage you were redirected to: " redirect = raw_input() - query = urlparse.parse_qs(urlparse.urlparse(redirect).query) - assert query['state'][0] == state, (query['state'][0], state) - code = query['code'][0] - - url = ("https://www.linkedin.com/uas/oauth2/accessToken" - "?grant_type=authorization_code" - "&code=%s&redirect_uri=%s&client_id=%s&client_secret=%s" % ( - code, api['REDIRECT_URI'], api['CLIENT_ID'], - api['CLIENT_SECRET'])) - - try: - response = urllib2.urlopen(url).read() - except urllib2.HTTPError, error: - print "!!ERROR!!" - print error - print error.read() - raise CommandError("Unable to retrieve access token") - - access_token = json.loads(response)['access_token'] - try: - tokens = LinkedInTokens.objects.get() - tokens.access_token = access_token - tokens.authorization_code = code - except LinkedInTokens.DoesNotExist: - tokens = LinkedInTokens( - access_token=access_token, - authorization_code=code) - tokens.save() + code = api.get_authorization_code(redirect) + api.get_access_token(code) diff --git a/lms/djangoapps/linkedin/models.py b/lms/djangoapps/linkedin/models.py index 9fb7b923a2..51e001effa 100644 --- a/lms/djangoapps/linkedin/models.py +++ b/lms/djangoapps/linkedin/models.py @@ -14,10 +14,9 @@ class LinkedIn(models.Model): emailed_courses = models.TextField(default="[]") # JSON list of course ids -class LinkedInTokens(models.Model): +class LinkedInToken(models.Model): """ For storing access token and authorization code after logging in to LinkedIn. """ access_token = models.CharField(max_length=255) - authorization_code = models.CharField(max_length=255)