Statistics
| Branch: | Tag: | Revision:

root / snf-astakos-app / astakos / im / target / twitter.py @ bef3bf46

History | View | Annotate | Download (10.4 kB)

1
# Copyright 2011-2012 GRNET S.A. All rights reserved.
2
#
3
# Redistribution and use in source and binary forms, with or
4
# without modification, are permitted provided that the following
5
# conditions are met:
6
#
7
#   1. Redistributions of source code must retain the above
8
#      copyright notice, this list of conditions and the following
9
#      disclaimer.
10
#
11
#   2. Redistributions in binary form must reproduce the above
12
#      copyright notice, this list of conditions and the following
13
#      disclaimer in the documentation and/or other materials
14
#      provided with the distribution.
15
#
16
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
17
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
20
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
23
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
24
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
26
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27
# POSSIBILITY OF SUCH DAMAGE.
28
#
29
# The views and conclusions contained in the software and
30
# documentation are those of the authors and should not be
31
# interpreted as representing official policies, either expressed
32
# or implied, of GRNET S.A.
33

    
34
# This is based on the docs at: https://github.com/simplegeo/python-oauth2
35

    
36
import oauth2 as oauth
37
import urlparse
38

    
39
from django.http import HttpResponse
40
from django.utils import simplejson as json
41
from django.contrib import messages
42

    
43
from astakos.im.util import get_context, prepare_response
44
from astakos.im.models import AstakosUser, Invitation
45
from astakos.im.views import render_response, requires_anonymous
46
from astakos.im.forms import LocalUserCreationForm, ThirdPartyUserCreationForm
47
from astakos.im.faults import BadRequest
48
from astakos.im.backends import get_backend
49
from astakos.im.settings import TWITTER_KEY, TWITTER_SECRET, INVITATIONS_ENABLED, IM_MODULES
50

    
51
# It's probably a good idea to put your consumer's OAuth token and
52
# OAuth secret into your project's settings.
53
consumer = oauth.Consumer(TWITTER_KEY, TWITTER_SECRET)
54
client = oauth.Client(consumer)
55

    
56
request_token_url = 'http://twitter.com/oauth/request_token'
57
access_token_url = 'http://twitter.com/oauth/access_token'
58

    
59
# This is the slightly different URL used to authenticate/authorize.
60
authenticate_url = 'http://twitter.com/oauth/authenticate'
61

    
62
@requires_anonymous
63
def login(request, extra_context={}):
64
    # store invitation code and email
65
    request.session['email'] = request.GET.get('email')
66
    request.session['invitation_code'] = request.GET.get('code')
67

    
68
    # Step 1. Get a request token from Twitter.
69
    resp, content = client.request(request_token_url, "GET")
70
    if resp['status'] != '200':
71
        raise Exception("Invalid response from Twitter.")
72
    request_token = dict(urlparse.parse_qsl(content))
73
    if request.GET.get('next'):
74
        request_token['next'] = request.GET['next']
75

    
76
    # Step 2. Store the request token in a session for later use.
77
    response = HttpResponse()
78
    request.session['Twitter-Request-Token'] = value=json.dumps(request_token)
79

    
80
    # Step 3. Redirect the user to the authentication URL.
81
    url = "%s?oauth_token=%s" % (authenticate_url, request_token['oauth_token'])
82
    response['Location'] = url
83
    response.status_code = 302
84

    
85
    return response
86

    
87
@requires_anonymous
88
def authenticated(request, backend=None, login_template='im/login.html', on_signup_failure='im/signup.html', on_signup_success='im/signup_complete.html', extra_context={}):
89
    # Step 1. Use the request token in the session to build a new client.
90
    data = request.session.get('Twitter-Request-Token')
91
    if not data:
92
        raise Exception("Request token cookie not found.")
93
    del request.session['Twitter-Request-Token']
94

    
95
    request_token = json.loads(data)
96
    if not hasattr(request_token, '__getitem__'):
97
        raise BadRequest('Invalid data formating')
98
    try:
99
        token = oauth.Token(request_token['oauth_token'],
100
                            request_token['oauth_token_secret'])
101
    except:
102
        raise BadRequest('Invalid request token cookie formatting')
103
    client = oauth.Client(consumer, token)
104

    
105
    # Step 2. Request the authorized access token from Twitter.
106
    resp, content = client.request(access_token_url, "GET")
107
    if resp['status'] != '200':
108
        raise Exception("Invalid response from Twitter.")
109

    
110
    """
111
    This is what you'll get back from Twitter. Note that it includes the
112
    user's user_id and screen_name.
113
    {
114
        'oauth_token_secret': 'IcJXPiJh8be3BjDWW50uCY31chyhsMHEhqJVsphC3M',
115
        'user_id': '120889797',
116
        'oauth_token': '120889797-H5zNnM3qE0iFoTTpNEHIz3noL9FKzXiOxwtnyVOD',
117
        'screen_name': 'heyismysiteup'
118
    }
119
    """
120
    access_token = dict(urlparse.parse_qsl(content))
121

    
122
    # Step 3. Lookup the user or create them if they don't exist.
123

    
124
    # When creating the user I just use their screen_name@twitter.com
125
    # for their email and the oauth_token_secret for their password.
126
    # These two things will likely never be used. Alternatively, you
127
    # can prompt them for their email here. Either way, the password
128
    # should never be used.
129
    screen_name = access_token['screen_name']
130
    next = request_token.get('next')
131

    
132
    # check first if user with that email is registered
133
    # and if not create one
134
    user = None
135
    email = request.session.pop('email')
136

    
137
    if email: # signup mode
138
        if not reserved_screen_name(screen_name):
139
            try:
140
                user = AstakosUser.objects.get(email = email)
141
            except AstakosUser.DoesNotExist, e:
142
                # register a new user
143
                post_data = {'provider':'Twitter', 'affiliation':'twitter',
144
                                'third_party_identifier':screen_name}
145
                form = ThirdPartyUserCreationForm({'email':email})
146
                return create_user(request, form, backend, post_data, next, on_signup_failure, on_signup_success, extra_context)
147
        else:
148
            status = messages.ERROR
149
            message = '%s@twitter is already registered' % screen_name
150
            messages.add_message(request, messages.ERROR, message)
151
            prefix = 'Invited' if request.session['invitation_code'] else ''
152
            suffix  = 'UserCreationForm'
153
            for provider in IM_MODULES:
154
                main = provider.capitalize() if provider == 'local' else 'ThirdParty'
155
                formclass = '%s%s%s' % (prefix, main, suffix)
156
                extra_context['%s_form' % provider] = globals()[formclass]()
157
            return render_response(on_signup_failure,
158
                                   context_instance=get_context(request, extra_context))
159
    else: # login mode
160
        try:
161
            user = AstakosUser.objects.get(third_party_identifier = screen_name,
162
                                           provider = 'Twitter')
163
        except AstakosUser.DoesNotExist:
164
            messages.add_message(request, messages.ERROR, 'Not registered user')
165
        if user and user.is_active:
166
            return prepare_response(request, user, next)
167
        elif user and not user.is_active:
168
            messages.add_message(request, messages.ERROR, 'Inactive account: %s' % user.email)
169
    ip = request.META.get('REMOTE_ADDR',
170
            request.META.get('HTTP_X_REAL_IP', None))
171
    return render_response(login_template,
172
                   form = LocalUserCreationForm(ip=ip),
173
                   context_instance=get_context(request, extra_context))
174

    
175
def reserved_screen_name(screen_name):
176
    try:
177
        AstakosUser.objects.get(provider='Twitter',
178
                                third_party_identifier=screen_name)
179
        return True
180
    except AstakosUser.DoesNotExist, e:
181
        return False
182

    
183
def create_user(request, form, backend=None, post_data={}, next = None, on_failure='im/signup.html', on_success='im/signup_complete.html', extra_context={}):
184
    """
185
    Create a user.
186

187
    The user activation will be delegated to the backend specified by the ``backend`` keyword argument
188
    if present, otherwise to the ``astakos.im.backends.InvitationBackend``
189
    if settings.ASTAKOS_INVITATIONS_ENABLED is True or ``astakos.im.backends.SimpleBackend`` if not
190
    (see backends);
191

192
    Upon successful user creation if ``next`` url parameter is present the user is redirected there
193
    otherwise renders the ``on_success`` template (if exists) or im/signup_complete.html.
194

195
    On unsuccessful creation, renders the ``on_failure`` template (if exists) or im/signup.html with an error message.
196

197
    **Arguments**
198

199
    ``on_failure``
200
        A custom template to render in case of failure. This is optional;
201
        if not specified, this will default to ``im/signup.html``.
202

203
    ``on_success``
204
        A custom template to render in case of success. This is optional;
205
        if not specified, this will default to ``im/signup_complete.html``.
206

207
    ``extra_context``
208
        An dictionary of variables to add to the template context.
209

210
    **Template:**
211

212
    im/signup.html or ``on_failure`` keyword argument.
213
    im/signup_complete.html or ``on_success`` keyword argument.
214
    """
215
    try:
216
        if not backend:
217
            backend = get_backend(request)
218
        if form.is_valid():
219
            status, message, user = backend.signup(form)
220
            if status == messages.SUCCESS:
221
                for k,v in post_data.items():
222
                    setattr(user,k, v)
223
                user.save()
224
                if user.is_active:
225
                    return prepare_response(request, user, next=next)
226
            messages.add_message(request, status, message)
227
            return render_response(on_success,
228
                                   context_instance=get_context(request, extra_context))
229
        else:
230
            messages.add_message(request, messages.ERROR, form.errors)
231
    except (Invitation.DoesNotExist, ValueError), e:
232
        messages.add_message(request, messages.ERROR, e)
233
    for provider in IM_MODULES:
234
        extra_context['%s_form' % provider] = backend.get_signup_form(provider)
235
    ip = request.META.get('REMOTE_ADDR',
236
            request.META.get('HTTP_X_REAL_IP', None))
237
    return render_response(on_failure,
238
                           form = LocalUserCreationForm(ip=ip),
239
                           context_instance=get_context(request, extra_context))