root / social_auth / backends / contrib / linkedin.py @ b5a2ce2a
History | View | Annotate | Download (4.7 kB)
1 |
"""
|
---|---|
2 |
Linkedin OAuth support
|
3 |
|
4 |
No extra configurations are needed to make this work.
|
5 |
"""
|
6 |
from xml.etree import ElementTree |
7 |
from xml.parsers.expat import ExpatError |
8 |
|
9 |
from oauth2 import Token |
10 |
|
11 |
from social_auth.utils import setting |
12 |
from social_auth.backends import ConsumerBasedOAuth, OAuthBackend, USERNAME |
13 |
from social_auth.exceptions import AuthCanceled, AuthUnknownError |
14 |
|
15 |
|
16 |
LINKEDIN_SERVER = 'linkedin.com'
|
17 |
LINKEDIN_REQUEST_TOKEN_URL = 'https://api.%s/uas/oauth/requestToken' % \
|
18 |
LINKEDIN_SERVER |
19 |
LINKEDIN_ACCESS_TOKEN_URL = 'https://api.%s/uas/oauth/accessToken' % \
|
20 |
LINKEDIN_SERVER |
21 |
LINKEDIN_AUTHORIZATION_URL = 'https://www.%s/uas/oauth/authenticate' % \
|
22 |
LINKEDIN_SERVER |
23 |
LINKEDIN_CHECK_AUTH = 'https://api.%s/v1/people/~' % LINKEDIN_SERVER
|
24 |
# Check doc at http://developer.linkedin.com/docs/DOC-1014 about how to use
|
25 |
# fields selectors to retrieve extra user data
|
26 |
LINKEDIN_FIELD_SELECTORS = ['id', 'first-name', 'last-name'] |
27 |
|
28 |
|
29 |
class LinkedinBackend(OAuthBackend): |
30 |
"""Linkedin OAuth authentication backend"""
|
31 |
name = 'linkedin'
|
32 |
EXTRA_DATA = [('id', 'id'), |
33 |
('first-name', 'first_name'), |
34 |
('last-name', 'last_name')] |
35 |
|
36 |
def get_user_details(self, response): |
37 |
"""Return user details from Linkedin account"""
|
38 |
first_name, last_name = response['first-name'], response['last-name'] |
39 |
email = response.get('email-address', '') |
40 |
return {USERNAME: first_name + last_name,
|
41 |
'fullname': first_name + ' ' + last_name, |
42 |
'first_name': first_name,
|
43 |
'last_name': last_name,
|
44 |
'email': email}
|
45 |
|
46 |
|
47 |
class LinkedinAuth(ConsumerBasedOAuth): |
48 |
"""Linkedin OAuth authentication mechanism"""
|
49 |
AUTHORIZATION_URL = LINKEDIN_AUTHORIZATION_URL |
50 |
REQUEST_TOKEN_URL = LINKEDIN_REQUEST_TOKEN_URL |
51 |
ACCESS_TOKEN_URL = LINKEDIN_ACCESS_TOKEN_URL |
52 |
AUTH_BACKEND = LinkedinBackend |
53 |
SETTINGS_KEY_NAME = 'LINKEDIN_CONSUMER_KEY'
|
54 |
SETTINGS_SECRET_NAME = 'LINKEDIN_CONSUMER_SECRET'
|
55 |
SCOPE_VAR_NAME = 'LINKEDIN_SCOPE'
|
56 |
SCOPE_SEPARATOR = '+'
|
57 |
|
58 |
def user_data(self, access_token, *args, **kwargs): |
59 |
"""Return user data provided"""
|
60 |
fields_selectors = LINKEDIN_FIELD_SELECTORS + \ |
61 |
setting('LINKEDIN_EXTRA_FIELD_SELECTORS', [])
|
62 |
# use set() over fields_selectors since LinkedIn fails when values are
|
63 |
# duplicated
|
64 |
url = LINKEDIN_CHECK_AUTH + ':(%s)' % ','.join(set(fields_selectors)) |
65 |
request = self.oauth_request(access_token, url)
|
66 |
raw_xml = self.fetch_response(request)
|
67 |
try:
|
68 |
return to_dict(ElementTree.fromstring(raw_xml))
|
69 |
except (ExpatError, KeyError, IndexError): |
70 |
return None |
71 |
|
72 |
def auth_complete(self, *args, **kwargs): |
73 |
"""Complete auth process. Check LinkedIn error response."""
|
74 |
oauth_problem = self.request.GET.get('oauth_problem') |
75 |
if oauth_problem:
|
76 |
if oauth_problem == 'user_refused': |
77 |
raise AuthCanceled(self, '') |
78 |
else:
|
79 |
raise AuthUnknownError(self, 'LinkedIn error was %s' % |
80 |
oauth_problem) |
81 |
return super(LinkedinAuth, self).auth_complete(*args, **kwargs) |
82 |
|
83 |
def get_scope(self): |
84 |
"""Return list with needed access scope"""
|
85 |
scope = [] |
86 |
if self.SCOPE_VAR_NAME: |
87 |
scope = setting(self.SCOPE_VAR_NAME, [])
|
88 |
else:
|
89 |
scope = [] |
90 |
return scope
|
91 |
|
92 |
def unauthorized_token(self): |
93 |
"""Makes first request to oauth. Returns an unauthorized Token."""
|
94 |
request_token_url = self.REQUEST_TOKEN_URL
|
95 |
scope = self.get_scope()
|
96 |
if scope:
|
97 |
qs = 'scope=' + self.SCOPE_SEPARATOR.join(scope) |
98 |
request_token_url = request_token_url + '?' + qs
|
99 |
|
100 |
request = self.oauth_request(
|
101 |
token=None,
|
102 |
url=request_token_url, |
103 |
extra_params=self.request_token_extra_arguments()
|
104 |
) |
105 |
response = self.fetch_response(request)
|
106 |
return Token.from_string(response)
|
107 |
|
108 |
|
109 |
def to_dict(xml): |
110 |
"""Convert XML structure to dict recursively, repeated keys entries
|
111 |
are returned as in list containers."""
|
112 |
children = xml.getchildren() |
113 |
if not children: |
114 |
return xml.text
|
115 |
else:
|
116 |
out = {} |
117 |
for node in xml.getchildren(): |
118 |
if node.tag in out: |
119 |
if not isinstance(out[node.tag], list): |
120 |
out[node.tag] = [out[node.tag]] |
121 |
out[node.tag].append(to_dict(node)) |
122 |
else:
|
123 |
out[node.tag] = to_dict(node) |
124 |
return out
|
125 |
|
126 |
|
127 |
# Backend definition
|
128 |
BACKENDS = { |
129 |
'linkedin': LinkedinAuth,
|
130 |
} |