Revision c101b32b

b/snf-astakos-app/astakos/im/auth_providers.py
152 152
    login_prompt_template = 'im/auth/shibboleth_login_prompt.html'
153 153

  
154 154

  
155
class TwitterAuthProvider(AuthProvider):
156
    module = 'twitter'
157
    title = _('Twitter')
158
    description = _('Allows you to login to your account using your twitter '
159
                    'account')
160
    add_prompt = _('Connect with your Twitter account.')
161

  
162
    @property
163
    def add_url(self):
164
        return reverse('astakos.im.target.twitter.login')
165

  
166
    login_template = 'im/auth/twitter_login.html'
167
    login_prompt_template = 'im/auth/twitter_login_prompt.html'
168

  
155 169
def get_provider(id, user_obj=None, default=None):
156 170
    """
157 171
    Return a provider instance from the auth providers registry.
b/snf-astakos-app/astakos/im/settings.py
5 5
AUTH_TOKEN_DURATION = getattr(settings, 'ASTAKOS_AUTH_TOKEN_DURATION', 30 * 24)
6 6

  
7 7
# Authenticate via Twitter.
8
TWITTER_KEY = getattr(settings, 'ASTAKOS_TWITTER_KEY', '')
8
TWITTER_TOKEN = getattr(settings, 'ASTAKOS_TWITTER_TOKEN', '')
9 9
TWITTER_SECRET = getattr(settings, 'ASTAKOS_TWITTER_SECRET', '')
10 10

  
11 11
DEFAULT_USER_LEVEL = getattr(settings, 'ASTAKOS_DEFAULT_USER_LEVEL', 4)
b/snf-astakos-app/astakos/im/target/twitter.py
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
from django.http import HttpResponseBadRequest
35
from django.utils.translation import ugettext as _
36
from django.contrib import messages
37
from django.template import RequestContext
38
from django.views.decorators.http import require_http_methods
39
from django.http import HttpResponseRedirect
40
from django.core.urlresolvers import reverse
41
from django.core.exceptions import ImproperlyConfigured
42
from django.shortcuts import get_object_or_404
43

  
44
from urlparse import urlunsplit, urlsplit
45

  
46
from astakos.im.util import prepare_response, get_context
47
from astakos.im.views import requires_anonymous, render_response, \
48
        requires_auth_provider
49
from astakos.im.settings import ENABLE_LOCAL_ACCOUNT_MIGRATION, BASEURL
50
from astakos.im.models import AstakosUser, PendingThirdPartyUser
51
from astakos.im.forms import LoginForm
52
from astakos.im.activation_backends import get_backend, SimpleBackend
53
from astakos.im import settings
54

  
55
import astakos.im.messages as astakos_messages
56

  
57
import logging
58

  
59
logger = logging.getLogger(__name__)
60

  
61
import oauth2 as oauth
62
import cgi
63

  
64
consumer = oauth.Consumer(settings.TWITTER_TOKEN, settings.TWITTER_SECRET)
65
client = oauth.Client(consumer)
66

  
67
request_token_url = 'http://twitter.com/oauth/request_token'
68
access_token_url = 'http://twitter.com/oauth/access_token'
69
authenticate_url = 'http://twitter.com/oauth/authenticate'
70

  
71

  
72
@requires_auth_provider('twitter', login=True)
73
@require_http_methods(["GET", "POST"])
74
def login(request):
75
    resp, content = client.request(request_token_url, "GET")
76
    if resp['status'] != '200':
77
        messages.error(request, 'Invalid Twitter response')
78
        return HttpResponseRedirect(reverse('edit_profile'))
79

  
80
    request.session['request_token'] = dict(cgi.parse_qsl(content))
81
    url = "%s?oauth_token=%s" % (authenticate_url,
82
        request.session['request_token']['oauth_token'])
83

  
84
    return HttpResponseRedirect(url)
85

  
86

  
87
@requires_auth_provider('twitter', login=True)
88
@require_http_methods(["GET", "POST"])
89
def authenticated(
90
    request,
91
    template='im/third_party_check_local.html',
92
    extra_context={}
93
):
94

  
95
    if not 'request_token' in request.session:
96
        messages.error(request, 'Twitter handshake failed')
97
        return HttpResponseRedirect(reverse('edit_profile'))
98

  
99
    token = oauth.Token(request.session['request_token']['oauth_token'],
100
        request.session['request_token']['oauth_token_secret'])
101
    client = oauth.Client(consumer, token)
102

  
103
    # Step 2. Request the authorized access token from Twitter.
104
    resp, content = client.request(access_token_url, "GET")
105
    if resp['status'] != '200':
106
        try:
107
          del request.session['request_token']
108
        except:
109
          pass
110
        messages.error(request, 'Invalid Twitter response')
111
        return HttpResponseRedirect(reverse('edit_profile'))
112

  
113
    access_token = dict(cgi.parse_qsl(content))
114
    userid = access_token['user_id']
115

  
116
    # an existing user accessed the view
117
    if request.user.is_authenticated():
118
        if request.user.has_auth_provider('twitter', identifier=userid):
119
            return HttpResponseRedirect(reverse('edit_profile'))
120

  
121
        # automatically add eppn provider to user
122
        user = request.user
123
        if not request.user.can_add_auth_provider('twitter',
124
                                                  identifier=userid):
125
            messages.error(request, 'Account already exists.')
126
            return HttpResponseRedirect(reverse('edit_profile'))
127

  
128
        user.add_auth_provider('twitter', identifier=userid)
129
        return HttpResponseRedirect(reverse('edit_profile'))
130

  
131
    try:
132
        # astakos user exists ?
133
        user = AstakosUser.objects.get_auth_provider_user(
134
            'twitter',
135
            identifier=userid
136
        )
137
        if user.is_active:
138
            # authenticate user
139
            return prepare_response(request,
140
                                    user,
141
                                    request.GET.get('next'),
142
                                    'renew' in request.GET)
143
        elif not user.activation_sent:
144
            message = _('Your request is pending activation')
145
			#TODO: use astakos_messages
146
            if not settings.MODERATION_ENABLED:
147
                url = user.get_resend_activation_url()
148
                msg_extra = _('<a href="%s">Resend activation email?</a>') % url
149
                message = message + u' ' + msg_extra
150

  
151
            messages.error(request, message)
152
            return HttpResponseRedirect(reverse('login'))
153

  
154
        else:
155
			#TODO: use astakos_messages
156
            message = _(u'Account disabled. Please contact support')
157
            messages.error(request, message)
158
            return HttpResponseRedirect(reverse('login'))
159

  
160
    except AstakosUser.DoesNotExist, e:
161
		#TODO: use astakos_messages
162
        # eppn not stored in astakos models, create pending profile
163
        user, created = PendingThirdPartyUser.objects.get_or_create(
164
            third_party_identifier=userid,
165
            provider='twitter',
166
        )
167
        # update pending user
168
        user.affiliation = 'Twitter'
169
        user.generate_token()
170
        user.save()
171

  
172
        extra_context['provider'] = 'twitter'
173
        extra_context['token'] = user.token
174

  
175
        return render_response(
176
            template,
177
            context_instance=get_context(request, extra_context)
178
        )
179

  
180

  
181
@requires_auth_provider('twitter', login=True, create=True)
182
@require_http_methods(["GET"])
183
@requires_anonymous
184
def signup(
185
    request,
186
    token,
187
    backend=None,
188
    on_creation_template='im/third_party_registration.html',
189
    extra_context={}):
190

  
191
    extra_context = extra_context or {}
192
    if not token:
193
		#TODO: use astakos_messages
194
        return HttpResponseBadRequest(_('Missing key parameter.'))
195

  
196
    pending = get_object_or_404(PendingThirdPartyUser, token=token)
197
    d = pending.__dict__
198
    d.pop('_state', None)
199
    d.pop('id', None)
200
    d.pop('token', None)
201
    d.pop('created', None)
202
    user = AstakosUser(**d)
203

  
204
    try:
205
        backend = backend or get_backend(request)
206
    except ImproperlyConfigured, e:
207
        messages.error(request, e)
208
    else:
209
        extra_context['form'] = backend.get_signup_form(
210
            provider='twitter',
211
            instance=user
212
        )
213

  
214
    extra_context['provider'] = 'twitter'
215
    extra_context['third_party_token'] = token
216
    return render_response(
217
            on_creation_template,
218
            context_instance=get_context(request, extra_context)
219
    )
220

  
b/snf-astakos-app/astakos/im/templates/im/auth/twitter_login.html
1
<h2><a href="/im/login/shibboleth">LOGIN OR CREATE ACCOUNT USING SHIBBOLETH</a></h2>
b/snf-astakos-app/astakos/im/templates/im/auth/twitter_login_prompt.html
1
<br />LOGIN or SIGNUP using
2
<a href="/im/login/twitter?{% ifnotequal next "" %}&next={{ next|urlencode }}{% endifnotequal %}{% ifnotequal code ""%}{% if next != "" %}&{% else %}?{% endif %}code={{ code }}{% endifnotequal %}"
3
  alt="{{ provider.get_title_display }}">{{ provider.get_title_display }}</a>
b/snf-astakos-app/astakos/im/urls.py
74 74
        url(r'^email_change/confirm/(?P<activation_key>\w+)/?$', 'change_email', {},
75 75
            name='email_change_confirm')
76 76
)
77
    
77

  
78 78
urlpatterns += patterns('astakos.im.target',
79 79
    url(r'^login/redirect/?$', 'redirect.login')
80 80
)
......
113 113
    urlpatterns += patterns('astakos.im.target',
114 114
                            url(r'^login/twitter/?$', 'twitter.login'),
115 115
                            url(r'^login/twitter/authenticated/?$',
116
                                'twitter.authenticated')
116
                                'twitter.authenticated'),
117
                            url(r'^twitter/signup/([\w-]+)/?$',
118
                                'twitter.signup', {}, 'twitter_signup')
117 119
                            )
118 120

  
119 121
urlpatterns += patterns('astakos.im.api',

Also available in: Unified diff