Statistics
| Branch: | Tag: | Revision:

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
}