Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (10.3 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
    return render_response(login_template,
170
                   form = LocalUserCreationForm(),
171
                   context_instance=get_context(request, extra_context))
172

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

    
181
def create_user(request, form, backend=None, post_data={}, next = None, on_failure='im/signup.html', on_success='im/signup_complete.html', extra_context={}): 
182
    """
183
    Create a user.
184
    
185
    The user activation will be delegated to the backend specified by the ``backend`` keyword argument
186
    if present, otherwise to the ``astakos.im.backends.InvitationBackend``
187
    if settings.ASTAKOS_INVITATIONS_ENABLED is True or ``astakos.im.backends.SimpleBackend`` if not
188
    (see backends);
189
    
190
    Upon successful user creation if ``next`` url parameter is present the user is redirected there
191
    otherwise renders the ``on_success`` template (if exists) or im/signup_complete.html.
192
    
193
    On unsuccessful creation, renders the ``on_failure`` template (if exists) or im/signup.html with an error message.
194
    
195
    **Arguments**
196
    
197
    ``on_failure``
198
        A custom template to render in case of failure. This is optional;
199
        if not specified, this will default to ``im/signup.html``.
200
    
201
    ``on_success``
202
        A custom template to render in case of success. This is optional;
203
        if not specified, this will default to ``im/signup_complete.html``.
204
    
205
    ``extra_context``
206
        An dictionary of variables to add to the template context.
207
    
208
    **Template:**
209
    
210
    im/signup.html or ``on_failure`` keyword argument.
211
    im/signup_complete.html or ``on_success`` keyword argument.
212
    """
213
    try:
214
        if not backend:
215
            backend = get_backend(request)
216
        if form.is_valid():
217
            status, message, user = backend.signup(form)
218
            if status == messages.SUCCESS:
219
                for k,v in post_data.items():
220
                    setattr(user,k, v)
221
                user.save()
222
                if user.is_active:
223
                    return prepare_response(request, user, next=next)
224
            messages.add_message(request, status, message)
225
            return render_response(on_success,
226
                                   context_instance=get_context(request, extra_context))
227
        else:
228
            messages.add_message(request, messages.ERROR, form.errors)
229
    except (Invitation.DoesNotExist, ValueError), e:
230
        messages.add_message(request, messages.ERROR, e)
231
    for provider in IM_MODULES:
232
        extra_context['%s_form' % provider] = backend.get_signup_form(provider)
233
    return render_response(on_failure,
234
                           form = LocalUserCreationForm(),
235
                           context_instance=get_context(request, extra_context))