Statistics
| Branch: | Tag: | Revision:

root / social / backends / odnoklassniki.py @ a0a04c0a

History | View | Annotate | Download (6.9 kB)

1
"""
2
Odnoklassniki OAuth2 and Iframe Application backends, docs at:
3
    http://psa.matiasaguirre.net/docs/backends/odnoklassnikiru.html
4
"""
5
from hashlib import md5
6

    
7
from social.p3 import unquote
8
from social.backends.base import BaseAuth
9
from social.backends.oauth import BaseOAuth2
10
from social.exceptions import AuthFailed
11

    
12

    
13
class OdnoklassnikiOAuth2(BaseOAuth2):
14
    """Odnoklassniki authentication backend"""
15
    name = 'odnoklassniki-oauth2'
16
    ID_KEY = 'uid'
17
    ACCESS_TOKEN_METHOD = 'POST'
18
    AUTHORIZATION_URL = 'http://www.odnoklassniki.ru/oauth/authorize'
19
    ACCESS_TOKEN_URL = 'http://api.odnoklassniki.ru/oauth/token.do'
20
    EXTRA_DATA = [('refresh_token', 'refresh_token'),
21
                  ('expires_in', 'expires')]
22

    
23
    def get_user_details(self, response):
24
        """Return user details from Odnoklassniki request"""
25
        fullname, first_name, last_name = self.get_user_names(
26
            fullname=unquote(response['name']),
27
            first_name=unquote(response['first_name']),
28
            last_name=unquote(response['last_name'])
29
        )
30
        return {
31
            'username': response['uid'],
32
            'email': '',
33
            'fullname': fullname,
34
            'first_name': first_name,
35
            'last_name': last_name
36
        }
37

    
38
    def user_data(self, access_token, *args, **kwargs):
39
        """Return user data from Odnoklassniki REST API"""
40
        data = {'access_token': access_token, 'method': 'users.getCurrentUser'}
41
        key, secret = self.get_key_and_secret()
42
        public_key = self.setting('PUBLIC_NAME')
43
        return odnoklassniki_api(self, data, 'http://api.odnoklassniki.ru/',
44
                                 public_key, secret, 'oauth')
45

    
46

    
47
class OdnoklassnikiApp(BaseAuth):
48
    """Odnoklassniki iframe app authentication backend"""
49
    name = 'odnoklassniki-app'
50
    ID_KEY = 'uid'
51

    
52
    def extra_data(self, user, uid, response, details):
53
        return dict([(key, value) for key, value in response.items()
54
                            if key in response['extra_data_list']])
55

    
56
    def get_user_details(self, response):
57
        fullname, first_name, last_name = self.get_user_names(
58
            fullname=unquote(response['name']),
59
            first_name=unquote(response['first_name']),
60
            last_name=unquote(response['last_name'])
61
        )
62
        return {
63
            'username': response['uid'],
64
            'email': '',
65
            'fullname': fullname,
66
            'first_name': first_name,
67
            'last_name': last_name
68
        }
69

    
70
    def auth_complete(self, request, user, *args, **kwargs):
71
        self.verify_auth_sig()
72
        response = self.get_response()
73
        fields = ('uid', 'first_name', 'last_name', 'name') + \
74
                 self.setting('EXTRA_USER_DATA_LIST', ())
75
        data = {
76
            'method': 'users.getInfo',
77
            'uids': '{0}'.format(response['logged_user_id']),
78
            'fields': ','.join(fields),
79
        }
80
        client_key, client_secret = self.get_key_and_secret()
81
        public_key = self.setting('PUBLIC_NAME')
82
        details = odnoklassniki_api(self, data, response['api_server'],
83
                                    public_key, client_secret,
84
                                    'iframe_nosession')
85
        if len(details) == 1 and 'uid' in details[0]:
86
            details = details[0]
87
            auth_data_fields = self.setting('EXTRA_AUTH_DATA_LIST',
88
                                            ('api_server', 'apiconnection',
89
                                             'session_key', 'authorized',
90
                                             'session_secret_key'))
91

    
92
            for field in auth_data_fields:
93
                details[field] = response[field]
94
            details['extra_data_list'] = fields + auth_data_fields
95
            kwargs.update({'backend': self, 'response': details})
96
        else:
97
            raise AuthFailed(self, 'Cannot get user details: API error')
98
        return self.strategy.authenticate(*args, **kwargs)
99

    
100
    def get_auth_sig(self):
101
        secret_key = self.setting('SECRET')
102
        hash_source = '{0:s}{1:s}{2:s}'.format(self.data['logged_user_id'],
103
                                               self.data['session_key'],
104
                                               secret_key)
105
        return md5(hash_source.encode('utf-8')).hexdigest()
106

    
107
    def get_response(self):
108
        fields = ('logged_user_id', 'api_server', 'application_key',
109
                  'session_key', 'session_secret_key', 'authorized',
110
                  'apiconnection')
111
        return dict((name, self.data[name]) for name in fields
112
                        if name in self.data)
113

    
114
    def verify_auth_sig(self):
115
        correct_key = self.get_auth_sig()
116
        key = self.data['auth_sig'].lower()
117
        if correct_key != key:
118
            raise AuthFailed(self, 'Wrong authorization key')
119

    
120

    
121
def odnoklassniki_oauth_sig(data, client_secret):
122
    """
123
    Calculates signature of request data access_token value must be included
124
    Algorithm is described at
125
        http://dev.odnoklassniki.ru/wiki/pages/viewpage.action?pageId=12878032,
126
    search for "little bit different way"
127
    """
128
    suffix = md5(
129
        '{0:s}{1:s}'.format(data['access_token'],
130
                            client_secret).encode('utf-8')
131
    ).hexdigest()
132
    check_list = sorted(['{0:s}={1:s}'.format(key, value)
133
                            for key, value in data.items()
134
                                if key != 'access_token'])
135
    return md5((''.join(check_list) + suffix).encode('utf-8')).hexdigest()
136

    
137

    
138
def odnoklassniki_iframe_sig(data, client_secret_or_session_secret):
139
    """
140
    Calculates signature as described at:
141
        http://dev.odnoklassniki.ru/wiki/display/ok/
142
            Authentication+and+Authorization
143
    If API method requires session context, request is signed with session
144
    secret key. Otherwise it is signed with application secret key
145
    """
146
    param_list = sorted(['{0:s}={1:s}'.format(key, value)
147
                            for key, value in data.items()])
148
    return md5(
149
        (''.join(param_list) + client_secret_or_session_secret).encode('utf-8')
150
    ).hexdigest()
151

    
152

    
153
def odnoklassniki_api(backend, data, api_url, public_key, client_secret,
154
                      request_type='oauth'):
155
    """Calls Odnoklassniki REST API method
156
    http://dev.odnoklassniki.ru/wiki/display/ok/Odnoklassniki+Rest+API"""
157
    data.update({
158
        'application_key': public_key,
159
        'format': 'JSON'
160
    })
161
    if request_type == 'oauth':
162
        data['sig'] = odnoklassniki_oauth_sig(data, client_secret)
163
    elif request_type == 'iframe_session':
164
        data['sig'] = odnoklassniki_iframe_sig(data,
165
                                               data['session_secret_key'])
166
    elif request_type == 'iframe_nosession':
167
        data['sig'] = odnoklassniki_iframe_sig(data, client_secret)
168
    else:
169
        msg = 'Unknown request type {0}. How should it be signed?'
170
        raise AuthFailed(backend, msg.format(request_type))
171
    return backend.get_json(api_url + 'fb.do', params=data)